/* $Id: fxddspan.c,v 1.22 2002/11/04 20:29:04 brianp Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  4.0
 *
 * Copyright (C) 1999-2001  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.
 */

/* Authors:
 *    David Bucciarelli
 *    Brian Paul
 *    Daryll Strauss
 *    Keith Whitwell
 */


/* fxdd.c - 3Dfx VooDoo Mesa span and pixel functions */


#ifdef HAVE_CONFIG_H
#include "conf.h"
#endif

#if defined(FX)

#include "fxdrv.h"
#include "swrast/swrast.h"

#ifdef _MSC_VER
#ifdef _WIN32
#pragma warning( disable : 4090 4022 )
/* 4101 : "different 'const' qualifier"
 * 4022 : "pointer mistmatch for actual parameter 'n'
 */
#endif
#endif


#if !defined(FXMESA_USE_ARGB)



#define writeRegionClipped(fxm,dst_buffer,dst_x,dst_y,src_format,src_width,src_height,src_stride,src_data)		\
  FX_grLfbWriteRegion(dst_buffer,dst_x,dst_y,src_format,src_width,src_height,src_stride,src_data)



/* KW: Rearranged the args in the call to grLfbWriteRegion().
 */
#define LFB_WRITE_SPAN_MESA(dst_buffer,		\
			    dst_x,		\
			    dst_y,		\
			    src_width,		\
			    src_stride,		\
			    src_data)		\
  writeRegionClipped(fxMesa, dst_buffer,	\
		   dst_x,			\
		   dst_y,			\
		   GR_LFB_SRC_FMT_8888,		\
		   src_width,			\
		   1,				\
		   src_stride,			\
		   src_data)			\


#else /* !defined(FXMESA_USE_RGBA) */

#define writeRegionClipped(fxm,dst_buffer,dst_x,dst_y,src_format,src_width,src_height,src_stride,src_data)		\
  FX_grLfbWriteRegion(dst_buffer,dst_x,dst_y,src_format,src_width,src_height,src_stride,src_data)


#define MESACOLOR_TO_ARGB(c) (				\
             ( ((unsigned int)(c[ACOMP]))<<24 ) |	\
             ( ((unsigned int)(c[RCOMP]))<<16 ) |	\
             ( ((unsigned int)(c[GCOMP]))<<8 )  |	\
             (  (unsigned int)(c[BCOMP])) )

inline void
LFB_WRITE_SPAN_MESA(GrBuffer_t dst_buffer,
		    FxU32 dst_x,
		    FxU32 dst_y,
		    FxU32 src_width, FxI32 src_stride, void *src_data)
{
   /* Covert to ARGB */
   GLubyte(*rgba)[4] = src_data;
   GLuint argb[MAX_WIDTH];
   int i;

   for (i = 0; i < src_width; i++) {
      argb[i] = MESACOLOR_TO_ARGB(rgba[i]);
   }
   writeRegionClipped( /*fxMesa, */ NULL, dst_buffer,
		      dst_x,
		      dst_y,
		      GR_LFB_SRC_FMT_8888,
		      src_width, 1, src_stride, (void *) argb);
}

#endif /* !defined(FXMESA_USE_RGBA) */


/************************************************************************/
/*****                    Span functions                            *****/
/************************************************************************/


static void
fxDDWriteRGBASpan(const GLcontext * ctx,
		  GLuint n, GLint x, GLint y,
		  const GLubyte rgba[][4], const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLuint i;
   GLint bottom = fxMesa->height - 1;

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDWriteRGBASpan(...)\n");
   }

   if (mask) {
      int span = 0;

      for (i = 0; i < n; i++) {
	 if (mask[i]) {
	    ++span;
	 }
	 else {
	    if (span > 0) {
	       LFB_WRITE_SPAN_MESA(fxMesa->currentFB, x + i - span,
				   bottom - y,
				   /* GR_LFB_SRC_FMT_8888, */ span, /*1, */ 0,
				   (void *) rgba[i - span]);
	       span = 0;
	    }
	 }
      }

      if (span > 0)
	 LFB_WRITE_SPAN_MESA(fxMesa->currentFB, x + n - span, bottom - y,
			     /* GR_LFB_SRC_FMT_8888, */ span, /*1, */ 0,
			     (void *) rgba[n - span]);
   }
   else
      LFB_WRITE_SPAN_MESA(fxMesa->currentFB, x, bottom - y,	/* GR_LFB_SRC_FMT_8888, */
			  n, /* 1, */ 0, (void *) rgba);
}


static void
fxDDWriteRGBSpan(const GLcontext * ctx,
		 GLuint n, GLint x, GLint y,
		 const GLubyte rgb[][3], const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLuint i;
   GLint bottom = fxMesa->height - 1;
   GLubyte rgba[MAX_WIDTH][4];

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDWriteRGBSpan()\n");
   }

   if (mask) {
      int span = 0;

      for (i = 0; i < n; i++) {
	 if (mask[i]) {
	    rgba[span][RCOMP] = rgb[i][0];
	    rgba[span][GCOMP] = rgb[i][1];
	    rgba[span][BCOMP] = rgb[i][2];
	    rgba[span][ACOMP] = 255;
	    ++span;
	 }
	 else {
	    if (span > 0) {
	       LFB_WRITE_SPAN_MESA(fxMesa->currentFB, x + i - span,
				   bottom - y,
				   /*GR_LFB_SRC_FMT_8888, */ span, /* 1, */ 0,
				   (void *) rgba);
	       span = 0;
	    }
	 }
      }

      if (span > 0)
	 LFB_WRITE_SPAN_MESA(fxMesa->currentFB, x + n - span, bottom - y,
			     /*GR_LFB_SRC_FMT_8888, */ span, /* 1, */ 0,
			     (void *) rgba);
   }
   else {
      for (i = 0; i < n; i++) {
	 rgba[i][RCOMP] = rgb[i][0];
	 rgba[i][GCOMP] = rgb[i][1];
	 rgba[i][BCOMP] = rgb[i][2];
	 rgba[i][ACOMP] = 255;
      }

      LFB_WRITE_SPAN_MESA(fxMesa->currentFB, x, bottom - y,	/* GR_LFB_SRC_FMT_8888, */
			  n, /* 1, */ 0, (void *) rgba);
   }
}


static void
fxDDWriteMonoRGBASpan(const GLcontext * ctx,
		      GLuint n, GLint x, GLint y,
		      const GLchan color[4], const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLuint i;
   GLint bottom = fxMesa->height - 1;
   GLuint data[MAX_WIDTH];
   GrColor_t gColor = FXCOLOR4(color);

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDWriteMonoRGBASpan(...)\n");
   }

   if (mask) {
      int span = 0;

      for (i = 0; i < n; i++) {
	 if (mask[i]) {
	    data[span] = (GLuint) gColor;
	    ++span;
	 }
	 else {
	    if (span > 0) {
	       writeRegionClipped(fxMesa, fxMesa->currentFB, x + i - span,
				  bottom - y, GR_LFB_SRC_FMT_8888, span, 1, 0,
				  (void *) data);
	       span = 0;
	    }
	 }
      }

      if (span > 0)
	 writeRegionClipped(fxMesa, fxMesa->currentFB, x + n - span,
			    bottom - y, GR_LFB_SRC_FMT_8888, span, 1, 0,
			    (void *) data);
   }
   else {
      for (i = 0; i < n; i++) {
	 data[i] = (GLuint) gColor;
      }

      writeRegionClipped(fxMesa, fxMesa->currentFB, x, bottom - y,
			 GR_LFB_SRC_FMT_8888, n, 1, 0, (void *) data);
   }
}


#if 0
static void
fxDDReadRGBASpan(const GLcontext * ctx,
		 GLuint n, GLint x, GLint y, GLubyte rgba[][4])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLushort data[MAX_WIDTH];
   GLuint i;
   GLint bottom = fxMesa->height - 1;

   printf("read span %d, %d, %d\n", x, y, n);
   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDReadRGBASpan(...)\n");
   }

   assert(n < MAX_WIDTH);

   FX_grLfbReadRegion(fxMesa->currentFB, x, bottom - y, n, 1, 0, data);

   for (i = 0; i < n; i++) {
      GLushort pixel = data[i];
      rgba[i][RCOMP] = FX_PixelToR[pixel];
      rgba[i][GCOMP] = FX_PixelToG[pixel];
      rgba[i][BCOMP] = FX_PixelToB[pixel];
      rgba[i][ACOMP] = 255;
   }
}
#endif


/*
 * Read a span of 16-bit RGB pixels.  Note, we don't worry about cliprects
 * since OpenGL says obscured pixels have undefined values.
 */
static void
read_R5G6B5_span(const GLcontext * ctx,
		 GLuint n, GLint x, GLint y, GLubyte rgba[][4])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GrLfbInfo_t info;
   BEGIN_BOARD_LOCK();
   if (grLfbLock(GR_LFB_READ_ONLY,
		 fxMesa->currentFB,
		 GR_LFBWRITEMODE_ANY, GR_ORIGIN_UPPER_LEFT, FXFALSE, &info)) {
      const GLint winX = 0;
      const GLint winY = fxMesa->height - 1;
      const GLint srcStride = info.strideInBytes / 2;	/* stride in GLushorts */
      const GLushort *data16 = (const GLushort *) info.lfbPtr
	 + (winY - y) * srcStride + (winX + x);
      const GLuint *data32 = (const GLuint *) data16;
      GLuint i, j;
      GLuint extraPixel = (n & 1);
      n -= extraPixel;
      for (i = j = 0; i < n; i += 2, j++) {
	 GLuint pixel = data32[j];
	 GLuint pixel0 = pixel & 0xffff;
	 GLuint pixel1 = pixel >> 16;
	 rgba[i][RCOMP] = FX_PixelToR[pixel0];
	 rgba[i][GCOMP] = FX_PixelToG[pixel0];
	 rgba[i][BCOMP] = FX_PixelToB[pixel0];
	 rgba[i][ACOMP] = 255;
	 rgba[i + 1][RCOMP] = FX_PixelToR[pixel1];
	 rgba[i + 1][GCOMP] = FX_PixelToG[pixel1];
	 rgba[i + 1][BCOMP] = FX_PixelToB[pixel1];
	 rgba[i + 1][ACOMP] = 255;
      }
      if (extraPixel) {
	 GLushort pixel = data16[n];
	 rgba[n][RCOMP] = FX_PixelToR[pixel];
	 rgba[n][GCOMP] = FX_PixelToG[pixel];
	 rgba[n][BCOMP] = FX_PixelToB[pixel];
	 rgba[n][ACOMP] = 255;
      }

      grLfbUnlock(GR_LFB_READ_ONLY, fxMesa->currentFB);
   }
   END_BOARD_LOCK();
}


/************************************************************************/
/*****                    Pixel functions                           *****/
/************************************************************************/

static void
fxDDWriteRGBAPixels(const GLcontext * ctx,
		    GLuint n, const GLint x[], const GLint y[],
		    CONST GLubyte rgba[][4], const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLuint i;
   GLint bottom = fxMesa->height - 1;

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDWriteRGBAPixels(...)\n");
   }

   for (i = 0; i < n; i++)
      if (mask[i])
	 LFB_WRITE_SPAN_MESA(fxMesa->currentFB, x[i], bottom - y[i],
			     1, 1, (void *) rgba[i]);
}

static void
fxDDWriteMonoRGBAPixels(const GLcontext * ctx,
			GLuint n, const GLint x[], const GLint y[],
			const GLchan color[4], const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLuint i;
   GLint bottom = fxMesa->height - 1;
   GrColor_t gColor = FXCOLOR4(color);

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDWriteMonoRGBAPixels(...)\n");
   }

   for (i = 0; i < n; i++)
      if (mask[i])
	 writeRegionClipped(fxMesa, fxMesa->currentFB, x[i], bottom - y[i],
			    GR_LFB_SRC_FMT_8888, 1, 1, 0, (void *) &gColor);
}


static void
read_R5G6B5_pixels(const GLcontext * ctx,
		   GLuint n, const GLint x[], const GLint y[],
		   GLubyte rgba[][4], const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GrLfbInfo_t info;
   BEGIN_BOARD_LOCK();
   if (grLfbLock(GR_LFB_READ_ONLY,
		 fxMesa->currentFB,
		 GR_LFBWRITEMODE_ANY, GR_ORIGIN_UPPER_LEFT, FXFALSE, &info)) {
      const GLint srcStride = info.strideInBytes / 2;	/* stride in GLushorts */
      const GLint winX = 0;
      const GLint winY = fxMesa->height - 1;
      GLuint i;
      for (i = 0; i < n; i++) {
	 if (mask[i]) {
	    const GLushort *data16 = (const GLushort *) info.lfbPtr
	       + (winY - y[i]) * srcStride + (winX + x[i]);
	    const GLushort pixel = *data16;
	    rgba[i][RCOMP] = FX_PixelToR[pixel];
	    rgba[i][GCOMP] = FX_PixelToG[pixel];
	    rgba[i][BCOMP] = FX_PixelToB[pixel];
	    rgba[i][ACOMP] = 255;
	 }
      }
      grLfbUnlock(GR_LFB_READ_ONLY, fxMesa->currentFB);
   }
   END_BOARD_LOCK();
}



/************************************************************************/
/*****                    Depth functions                           *****/
/************************************************************************/

void
fxDDWriteDepthSpan(GLcontext * ctx,
		   GLuint n, GLint x, GLint y, const GLdepth depth[],
		   const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLint bottom = fxMesa->height - 1;

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDWriteDepthSpan(...)\n");
   }


   if (mask) {
      GLint i;
      for (i = 0; i < n; i++) {
	 if (mask[i]) {
	    GLshort d = depth[i];
	    writeRegionClipped(fxMesa, GR_BUFFER_AUXBUFFER, x + i, bottom - y,
			       GR_LFB_SRC_FMT_ZA16, 1, 1, 0, (void *) &d);
	 }
      }
   }
   else {
      GLushort depth16[MAX_WIDTH];
      GLint i;
      for (i = 0; i < n; i++) {
	 depth16[i] = depth[i];
      }
      writeRegionClipped(fxMesa, GR_BUFFER_AUXBUFFER, x, bottom - y,
			 GR_LFB_SRC_FMT_ZA16, n, 1, 0, (void *) depth16);
   }
}


void
fxDDReadDepthSpan(GLcontext * ctx,
		  GLuint n, GLint x, GLint y, GLdepth depth[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLint bottom = fxMesa->height - 1;
   GLushort depth16[MAX_WIDTH];
   GLuint i;

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDReadDepthSpan(...)\n");
   }

   FX_grLfbReadRegion(GR_BUFFER_AUXBUFFER, x, bottom - y, n, 1, 0, depth16);
   for (i = 0; i < n; i++) {
      depth[i] = depth16[i];
   }
}



void
fxDDWriteDepthPixels(GLcontext * ctx,
		     GLuint n, const GLint x[], const GLint y[],
		     const GLdepth depth[], const GLubyte mask[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLint bottom = fxMesa->height - 1;
   GLuint i;

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDWriteDepthPixels(...)\n");
   }

   for (i = 0; i < n; i++) {
      if (mask[i]) {
	 int xpos = x[i];
	 int ypos = bottom - y[i];
	 GLushort d = depth[i];
	 writeRegionClipped(fxMesa, GR_BUFFER_AUXBUFFER, xpos, ypos,
			    GR_LFB_SRC_FMT_ZA16, 1, 1, 0, (void *) &d);
      }
   }
}


void
fxDDReadDepthPixels(GLcontext * ctx, GLuint n,
		    const GLint x[], const GLint y[], GLdepth depth[])
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   GLint bottom = fxMesa->height - 1;
   GLuint i;

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDReadDepthPixels(...)\n");
   }

   for (i = 0; i < n; i++) {
      int xpos = x[i];
      int ypos = bottom - y[i];
      GLushort d;
      FX_grLfbReadRegion(GR_BUFFER_AUXBUFFER, xpos, ypos, 1, 1, 0, &d);
      depth[i] = d;
   }
}



/* Set the buffer used for reading */
/* XXX support for separate read/draw buffers hasn't been tested */
static void
fxDDSetBuffer(GLcontext * ctx, GLframebuffer * buffer, GLuint bufferBit)
{
   fxMesaContext fxMesa = (fxMesaContext) ctx->DriverCtx;
   (void) buffer;

   if (MESA_VERBOSE & VERBOSE_DRIVER) {
      fprintf(stderr, "fxmesa: fxDDSetBuffer(%x)\n", (int) bufferBit);
   }

   if (bufferBit == FRONT_LEFT_BIT) {
      fxMesa->currentFB = GR_BUFFER_FRONTBUFFER;
      FX_grRenderBuffer(fxMesa->currentFB);
   }
   else if (bufferBit == BACK_LEFT_BIT) {
      fxMesa->currentFB = GR_BUFFER_BACKBUFFER;
      FX_grRenderBuffer(fxMesa->currentFB);
   }
}


/************************************************************************/



void
fxSetupDDSpanPointers(GLcontext * ctx)
{
   struct swrast_device_driver *swdd = _swrast_GetDeviceDriverReference( ctx );

   swdd->SetBuffer = fxDDSetBuffer;

   swdd->WriteRGBASpan = fxDDWriteRGBASpan;
   swdd->WriteRGBSpan = fxDDWriteRGBSpan;
   swdd->WriteMonoRGBASpan = fxDDWriteMonoRGBASpan;
   swdd->WriteRGBAPixels = fxDDWriteRGBAPixels;
   swdd->WriteMonoRGBAPixels = fxDDWriteMonoRGBAPixels;

   swdd->WriteDepthSpan = fxDDWriteDepthSpan;
   swdd->WriteDepthPixels = fxDDWriteDepthPixels;
   swdd->ReadDepthSpan = fxDDReadDepthSpan;
   swdd->ReadDepthPixels = fxDDReadDepthPixels;

   /*  swdd->ReadRGBASpan        =fxDDReadRGBASpan; */
   swdd->ReadRGBASpan = read_R5G6B5_span;
   swdd->ReadRGBAPixels = read_R5G6B5_pixels;
}


#else


/*
 * Need this to provide at least one external definition.
 */

extern int gl_fx_dummy_function_span(void);
int
gl_fx_dummy_function_span(void)
{
   return 0;
}

#endif /* FX */
