/* -*- mode: c; c-basic-offset: 3 -*-
 *
 * Copyright 2000 VA Linux Systems Inc., Fremont, California.
 *
 * 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 (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 NONINFRINGEMENT.  IN NO EVENT SHALL
 * VA LINUX SYSTEMS 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.
 */

/*
 * Original rewrite:
 *	Gareth Hughes <gareth@valinux.com>, 29 Sep - 1 Oct 2000
 *
 * Authors:
 *	Gareth Hughes <gareth@valinux.com>
 *
 */

#include "tdfx_dri.h"
#include "tdfx_context.h"
#include "tdfx_lock.h"
#include "tdfx_vb.h"
#include "tdfx_span.h"
#include "tdfx_tris.h"

#include "main/framebuffer.h"
#include "main/renderbuffer.h"
#include "xmlpool.h"

#include "utils.h"

#ifdef DEBUG_LOCKING
char *prevLockFile = 0;
int prevLockLine = 0;
#endif

#ifndef TDFX_DEBUG
int TDFX_DEBUG = 0;
#endif

PUBLIC const char __driConfigOptions[] =
DRI_CONF_BEGIN
    DRI_CONF_SECTION_DEBUG
        DRI_CONF_NO_RAST(false)
    DRI_CONF_SECTION_END
DRI_CONF_END;

static const __DRIextension *tdfxExtensions[] = {
    &driReadDrawableExtension,
    NULL
};

static const GLuint __driNConfigOptions = 1;

extern const struct dri_extension card_extensions[];
extern const struct dri_extension napalm_extensions[];

static GLboolean
tdfxCreateScreen( __DRIscreenPrivate *sPriv )
{
   tdfxScreenPrivate *fxScreen;
   TDFXDRIPtr fxDRIPriv = (TDFXDRIPtr) sPriv->pDevPriv;

   if (sPriv->devPrivSize != sizeof(TDFXDRIRec)) {
      fprintf(stderr,"\nERROR!  sizeof(TDFXDRIRec) does not match passed size from device driver\n");
      return GL_FALSE;
   }

   /* Allocate the private area */
   fxScreen = (tdfxScreenPrivate *) CALLOC( sizeof(tdfxScreenPrivate) );
   if ( !fxScreen )
      return GL_FALSE;

   /* parse information in __driConfigOptions */
   driParseOptionInfo (&fxScreen->optionCache,
		       __driConfigOptions, __driNConfigOptions);

   fxScreen->driScrnPriv = sPriv;
   sPriv->private = (void *) fxScreen;

   fxScreen->regs.handle	= fxDRIPriv->regs;
   fxScreen->regs.size		= fxDRIPriv->regsSize;
   fxScreen->deviceID		= fxDRIPriv->deviceID;
   fxScreen->width		= fxDRIPriv->width;
   fxScreen->height		= fxDRIPriv->height;
   fxScreen->mem		= fxDRIPriv->mem;
   fxScreen->cpp		= fxDRIPriv->cpp;
   fxScreen->stride		= fxDRIPriv->stride;
   fxScreen->fifoOffset		= fxDRIPriv->fifoOffset;
   fxScreen->fifoSize		= fxDRIPriv->fifoSize;
   fxScreen->fbOffset		= fxDRIPriv->fbOffset;
   fxScreen->backOffset		= fxDRIPriv->backOffset;
   fxScreen->depthOffset	= fxDRIPriv->depthOffset;
   fxScreen->textureOffset	= fxDRIPriv->textureOffset;
   fxScreen->textureSize	= fxDRIPriv->textureSize;
   fxScreen->sarea_priv_offset	= fxDRIPriv->sarea_priv_offset;

   if ( drmMap( sPriv->fd, fxScreen->regs.handle,
		fxScreen->regs.size, &fxScreen->regs.map ) ) {
      return GL_FALSE;
   }

   sPriv->extensions = tdfxExtensions;

   return GL_TRUE;
}


static void
tdfxDestroyScreen( __DRIscreenPrivate *sPriv )
{
   tdfxScreenPrivate *fxScreen = (tdfxScreenPrivate *) sPriv->private;

   if (!fxScreen)
      return;

   drmUnmap( fxScreen->regs.map, fxScreen->regs.size );

   /* free all option information */
   driDestroyOptionInfo (&fxScreen->optionCache);

   FREE( fxScreen );
   sPriv->private = NULL;
}


static GLboolean
tdfxInitDriver( __DRIscreenPrivate *sPriv )
{
   if ( TDFX_DEBUG & DEBUG_VERBOSE_DRI ) {
      fprintf( stderr, "%s( %p )\n", __FUNCTION__, (void *)sPriv );
   }

   if ( !tdfxCreateScreen( sPriv ) ) {
      tdfxDestroyScreen( sPriv );
      return GL_FALSE;
   }

   return GL_TRUE;
}


static GLboolean
tdfxCreateBuffer( __DRIscreenPrivate *driScrnPriv,
                  __DRIdrawablePrivate *driDrawPriv,
                  const __GLcontextModes *mesaVis,
                  GLboolean isPixmap )
{
   tdfxScreenPrivate *screen = (tdfxScreenPrivate *) driScrnPriv->private;

   if (isPixmap) {
      return GL_FALSE; /* not implemented */
   }
   else {
      struct gl_framebuffer *fb = _mesa_create_framebuffer(mesaVis);

      {
         driRenderbuffer *frontRb
            = driNewRenderbuffer(GL_RGBA, NULL, screen->cpp,
                                 screen->fbOffset, screen->width, driDrawPriv);
         tdfxSetSpanFunctions(frontRb, mesaVis);
         _mesa_add_renderbuffer(fb, BUFFER_FRONT_LEFT, &frontRb->Base);
      }

      if (mesaVis->doubleBufferMode) {
         driRenderbuffer *backRb
            = driNewRenderbuffer(GL_RGBA, NULL, screen->cpp,
                                 screen->backOffset, screen->width,
                                 driDrawPriv);
         tdfxSetSpanFunctions(backRb, mesaVis);
         _mesa_add_renderbuffer(fb, BUFFER_BACK_LEFT, &backRb->Base);
	 backRb->backBuffer = GL_TRUE;
      }

      if (mesaVis->depthBits == 16) {
         driRenderbuffer *depthRb
            = driNewRenderbuffer(GL_DEPTH_COMPONENT16, NULL, screen->cpp,
                                 screen->depthOffset, screen->width,
                                 driDrawPriv);
         tdfxSetSpanFunctions(depthRb, mesaVis);
         _mesa_add_renderbuffer(fb, BUFFER_DEPTH, &depthRb->Base);
      }
      else if (mesaVis->depthBits == 24) {
         driRenderbuffer *depthRb
            = driNewRenderbuffer(GL_DEPTH_COMPONENT24, NULL, screen->cpp,
                                 screen->depthOffset, screen->width,
                                 driDrawPriv);
         tdfxSetSpanFunctions(depthRb, mesaVis);
         _mesa_add_renderbuffer(fb, BUFFER_DEPTH, &depthRb->Base);
      }

      if (mesaVis->stencilBits > 0) {
         driRenderbuffer *stencilRb
            = driNewRenderbuffer(GL_STENCIL_INDEX8_EXT, NULL, screen->cpp,
                                 screen->depthOffset, screen->width,
                                 driDrawPriv);
         tdfxSetSpanFunctions(stencilRb, mesaVis);
         _mesa_add_renderbuffer(fb, BUFFER_STENCIL, &stencilRb->Base);
      }

      _mesa_add_soft_renderbuffers(fb,
                                   GL_FALSE, /* color */
                                   GL_FALSE, /* depth */
                                   GL_FALSE, /*swStencil,*/
                                   mesaVis->accumRedBits > 0,
                                   GL_FALSE, /* alpha */
                                   GL_FALSE /* aux */);
      driDrawPriv->driverPrivate = (void *) fb;

      return (driDrawPriv->driverPrivate != NULL);
   }
}


static void
tdfxDestroyBuffer(__DRIdrawablePrivate *driDrawPriv)
{
   _mesa_reference_framebuffer((GLframebuffer **)(&(driDrawPriv->driverPrivate)), NULL);
}


static void
tdfxSwapBuffers( __DRIdrawablePrivate *driDrawPriv )

{
   GET_CURRENT_CONTEXT(ctx);
   tdfxContextPtr fxMesa = 0;
   GLframebuffer *mesaBuffer;

   if ( TDFX_DEBUG & DEBUG_VERBOSE_DRI ) {
      fprintf( stderr, "%s( %p )\n", __FUNCTION__, (void *)driDrawPriv );
   }

   mesaBuffer = (GLframebuffer *) driDrawPriv->driverPrivate;
   if ( !mesaBuffer->Visual.doubleBufferMode )
      return; /* can't swap a single-buffered window */

   /* If the current context's drawable matches the given drawable
    * we have to do a glFinish (per the GLX spec).
    */
   if ( ctx ) {
      __DRIdrawablePrivate *curDrawPriv;
      fxMesa = TDFX_CONTEXT(ctx);
      curDrawPriv = fxMesa->driContext->driDrawablePriv;

      if ( curDrawPriv == driDrawPriv ) {
	 /* swapping window bound to current context, flush first */
	 _mesa_notifySwapBuffers( ctx );
	 LOCK_HARDWARE( fxMesa );
      }
      else {
         /* find the fxMesa context previously bound to the window */
	 fxMesa = (tdfxContextPtr) driDrawPriv->driContextPriv->driverPrivate;
         if (!fxMesa)
            return;
	 LOCK_HARDWARE( fxMesa );
	 fxMesa->Glide.grSstSelect( fxMesa->Glide.Board );
#ifdef DEBUG
         printf("SwapBuf SetState 1\n");
#endif
	 fxMesa->Glide.grGlideSetState(fxMesa->Glide.State );
      }
   }

#ifdef STATS
   {
      int stalls;
      static int prevStalls = 0;

      stalls = fxMesa->Glide.grFifoGetStalls();

      fprintf( stderr, "%s:\n", __FUNCTION__ );
      if ( stalls != prevStalls ) {
	 fprintf( stderr, "    %d stalls occurred\n",
		  stalls - prevStalls );
	 prevStalls = stalls;
      }
      if ( fxMesa && fxMesa->texSwaps ) {
	 fprintf( stderr, "    %d texture swaps occurred\n",
		  fxMesa->texSwaps );
	 fxMesa->texSwaps = 0;
      }
   }
#endif

   if (fxMesa->scissoredClipRects) {
      /* restore clip rects without scissor box */
      fxMesa->Glide.grDRIPosition( driDrawPriv->x, driDrawPriv->y,
                                   driDrawPriv->w, driDrawPriv->h,
                                   driDrawPriv->numClipRects,
                                   driDrawPriv->pClipRects );
   }

   fxMesa->Glide.grDRIBufferSwap( fxMesa->Glide.SwapInterval );

   if (fxMesa->scissoredClipRects) {
      /* restore clip rects WITH scissor box */
      fxMesa->Glide.grDRIPosition( driDrawPriv->x, driDrawPriv->y,
                                   driDrawPriv->w, driDrawPriv->h,
                                   fxMesa->numClipRects, fxMesa->pClipRects );
   }


#if 0
   {
      FxI32 result;
      do {
         FxI32 result;
         fxMesa->Glide.grGet(GR_PENDING_BUFFERSWAPS, 4, &result);
      } while ( result > fxMesa->maxPendingSwapBuffers );
   }
#endif

   fxMesa->stats.swapBuffer++;

   if (ctx) {
      if (ctx->DriverCtx != fxMesa) {
         fxMesa = TDFX_CONTEXT(ctx);
	 fxMesa->Glide.grSstSelect( fxMesa->Glide.Board );
#ifdef DEBUG
         printf("SwapBuf SetState 2\n");
#endif
	 fxMesa->Glide.grGlideSetState(fxMesa->Glide.State );
      }
      UNLOCK_HARDWARE( fxMesa );
   }
}

static const __DRIconfig **
tdfxFillInModes(__DRIscreenPrivate *psp,
		unsigned pixel_bits,
		unsigned depth_bits,
		unsigned stencil_bits,
		GLboolean have_back_buffer)
{
	unsigned deep = (depth_bits > 17);

	/* Right now GLX_SWAP_COPY_OML isn't supported, but it would be easy
	 * enough to add support.  Basically, if a context is created with an
	 * fbconfig where the swap method is GLX_SWAP_COPY_OML, pageflipping
	 * will never be used.
	 */

	static const GLenum db_modes[2] = { GLX_NONE, GLX_SWAP_UNDEFINED_OML };
	uint8_t depth_bits_array[4];
	uint8_t stencil_bits_array[4];
        uint8_t msaa_samples_array[1];
	if(deep) {
		depth_bits_array[0] = 0;
		depth_bits_array[1] = 24;
		stencil_bits_array[0] = 0;
		stencil_bits_array[1] = 8;
	} else {
		depth_bits_array[0] = depth_bits;
		depth_bits_array[1] = 0;
		depth_bits_array[2] = depth_bits;
		depth_bits_array[3] = 0;
		stencil_bits_array[0] = 0;
		stencil_bits_array[1] = 0;
		stencil_bits_array[2] = 8;
		stencil_bits_array[3] = 8;
	}

	msaa_samples_array[0] = 0;

	return (const __DRIconfig **)
	   driCreateConfigs(deep ? GL_RGBA : GL_RGB,
			    deep ? GL_UNSIGNED_INT_8_8_8_8 :
				   GL_UNSIGNED_SHORT_5_6_5,
			    depth_bits_array,
			    stencil_bits_array,
			    deep ? 2 : 4,
			    db_modes, 2,
			    msaa_samples_array, 1);
}

/**
 * This is the driver specific part of the createNewScreen entry point.
 * 
 * \todo maybe fold this into intelInitDriver
 *
 * \return the __GLcontextModes supported by this driver
 */
static const __DRIconfig **
tdfxInitScreen(__DRIscreen *psp)
{
   static const __DRIversion ddx_expected = { 1, 1, 0 };
   static const __DRIversion dri_expected = { 4, 0, 0 };
   static const __DRIversion drm_expected = { 1, 0, 0 };

   /* divined from tdfx_dri.c, sketchy */
   TDFXDRIPtr dri_priv = (TDFXDRIPtr) psp->pDevPriv;

   /* XXX i wish it was like this */
   /* bpp = dri_priv->bpp */
   int bpp = (dri_priv->cpp > 2) ? 24 : 16;

   if ( ! driCheckDriDdxDrmVersions2( "tdfx",
				      &psp->dri_version, & dri_expected,
				      &psp->ddx_version, & ddx_expected,
				      &psp->drm_version, & drm_expected ) )
      return NULL;

   /* Calling driInitExtensions here, with a NULL context pointer,
    * does not actually enable the extensions.  It just makes sure
    * that all the dispatch offsets for all the extensions that
    * *might* be enables are known.  This is needed because the
    * dispatch offsets need to be known when _mesa_context_create is
    * called, but we can't enable the extensions until we have a
    * context pointer.
    *
    * Hello chicken.  Hello egg.  How are you two today?
    */
   driInitExtensions( NULL, card_extensions, GL_FALSE );
   driInitExtensions( NULL, napalm_extensions, GL_FALSE );

   if (!tdfxInitDriver(psp))
      return NULL;
      
   return tdfxFillInModes(psp,
			  bpp, (bpp == 16) ? 16 : 24,
			  (bpp == 16) ? 0 : 8,
			  (dri_priv->backOffset!=dri_priv->depthOffset));
}

const struct __DriverAPIRec driDriverAPI = {
   .InitScreen      = tdfxInitScreen,
   .DestroyScreen   = tdfxDestroyScreen,
   .CreateContext   = tdfxCreateContext,
   .DestroyContext  = tdfxDestroyContext,
   .CreateBuffer    = tdfxCreateBuffer,
   .DestroyBuffer   = tdfxDestroyBuffer,
   .SwapBuffers     = tdfxSwapBuffers,
   .MakeCurrent     = tdfxMakeCurrent,
   .UnbindContext   = tdfxUnbindContext,
   .GetSwapInfo     = NULL,
   .GetDrawableMSC  = NULL,
   .WaitForMSC      = NULL,
   .WaitForSBC      = NULL,
   .SwapBuffersMSC  = NULL
};
