/*
 * Copyright 2001 by Alan Hourihane.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Alan Hourihane not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Alan Hourihane makes no representations
 * about the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * ALAN HOURIHANE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL ALAN HOURIHANE BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors:  Alan Hourihane, <alanh@tungstengraphics.com>
 *
 * 3DLabs Gamma driver
 */

#include "gammacontext.h"
#include "gamma_vb.h"
#include "main/context.h"
#include "main/matrix.h"
#include "glint_dri.h"

#include "swrast/swrast.h"
#include "swrast_setup/swrast_setup.h"
#include "tnl/tnl.h"
#include "vbo/vbo.h"

static GLboolean 
gammaInitDriver(__DRIscreenPrivate *sPriv)
{
    sPriv->private = (void *) gammaCreateScreen( sPriv );

    if (!sPriv->private) {
	gammaDestroyScreen( sPriv );
	return GL_FALSE;
    }

    return GL_TRUE;
}

static void 
gammaDestroyContext(__DRIcontextPrivate *driContextPriv)
{
    gammaContextPtr gmesa = (gammaContextPtr)driContextPriv->driverPrivate;

    if (gmesa) {
      _swsetup_DestroyContext( gmesa->glCtx );
      _tnl_DestroyContext( gmesa->glCtx );
      _vbo_DestroyContext( gmesa->glCtx );
      _swrast_DestroyContext( gmesa->glCtx );

      gammaFreeVB( gmesa->glCtx );

      /* free the Mesa context */
      gmesa->glCtx->DriverCtx = NULL;
      _mesa_destroy_context(gmesa->glCtx);

      FREE(gmesa);
      driContextPriv->driverPrivate = NULL;
    }
}


static GLboolean
gammaCreateBuffer( __DRIscreenPrivate *driScrnPriv,
                   __DRIdrawablePrivate *driDrawPriv,
                   const __GLcontextModes *mesaVis,
                   GLboolean isPixmap )
{
   if (isPixmap) {
      return GL_FALSE; /* not implemented */
   }
   else {
      driDrawPriv->driverPrivate = (void *) 
         _mesa_create_framebuffer(mesaVis,
                                  GL_FALSE,  /* software depth buffer? */
                                  mesaVis->stencilBits > 0,
                                  mesaVis->accumRedBits > 0,
                                  mesaVis->alphaBits > 0
                                  );
      return (driDrawPriv->driverPrivate != NULL);
   }
}


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

static void
gammaSwapBuffers( __DRIdrawablePrivate *dPriv )
{
   if (dPriv->driContextPriv && dPriv->driContextPriv->driverPrivate) {
    gammaContextPtr gmesa;
    __DRIscreenPrivate *driScrnPriv;
    GLcontext *ctx;

    gmesa = (gammaContextPtr) dPriv->driContextPriv->driverPrivate;
    ctx = gmesa->glCtx;
    driScrnPriv = gmesa->driScreen;

    _mesa_notifySwapBuffers(ctx);

    VALIDATE_DRAWABLE_INFO(gmesa);

    /* Flush any partially filled buffers */
    FLUSH_DMA_BUFFER(gmesa);

    DRM_SPINLOCK(&driScrnPriv->pSAREA->drawable_lock,
		     driScrnPriv->drawLockID);
    VALIDATE_DRAWABLE_INFO_NO_LOCK(gmesa);

    if (gmesa->EnabledFlags & GAMMA_BACK_BUFFER) {
	int src, dst, x0, y0, x1, h;
	int i;
	int nRect = dPriv->numClipRects;
	drm_clip_rect_t *pRect = dPriv->pClipRects;
	__DRIscreenPrivate *driScrnPriv = gmesa->driScreen;
   	GLINTDRIPtr gDRIPriv = (GLINTDRIPtr)driScrnPriv->pDevPriv;

	CHECK_DMA_BUFFER(gmesa, 2);
	WRITE(gmesa->buf, FBReadMode, (gmesa->FBReadMode |
					 FBReadSrcEnable));
	WRITE(gmesa->buf, LBWriteMode, LBWriteModeDisable);

	for (i = 0; i < nRect; i++, pRect++) {
	    x0 = pRect->x1;
	    x1 = pRect->x2;
	    h  = pRect->y2 - pRect->y1;

	    y0 = driScrnPriv->fbHeight - (pRect->y1+h);
	    if (gDRIPriv->numMultiDevices == 2) 
	    	src = (y0/2)*driScrnPriv->fbWidth+x0;
	    else
	    	src = y0*driScrnPriv->fbWidth+x0;

	    y0 += driScrnPriv->fbHeight;
	    if (gDRIPriv->numMultiDevices == 2) 
	    	dst = (y0/2)*driScrnPriv->fbWidth+x0;
	    else
	    	dst = y0*driScrnPriv->fbWidth+x0;

	    CHECK_DMA_BUFFER(gmesa, 9);
	    WRITE(gmesa->buf, StartXDom,       x0<<16);   /* X0dest */
	    WRITE(gmesa->buf, StartY,          y0<<16);   /* Y0dest */
	    WRITE(gmesa->buf, StartXSub,       x1<<16);   /* X1dest */
	    WRITE(gmesa->buf, GLINTCount,      h);        /* H */
	    WRITE(gmesa->buf, dY,              1<<16);    /* ydir */
	    WRITE(gmesa->buf, dXDom,           0<<16);
	    WRITE(gmesa->buf, dXSub,           0<<16);
	    WRITE(gmesa->buf, FBSourceOffset, (dst-src));
	    WRITE(gmesa->buf, Render,          0x00040048); /* NOT_DONE */
	}

	/*
	** NOTE: FBSourceOffset (above) is backwards from what is
	** described in the manual (i.e., dst-src instead of src-dst)
	** due to our using the bottom-left window origin instead of the
	** top-left window origin.
	*/

	/* Restore FBReadMode */
	CHECK_DMA_BUFFER(gmesa, 2);
	WRITE(gmesa->buf, FBReadMode, (gmesa->FBReadMode |
				       gmesa->AB_FBReadMode));
	WRITE(gmesa->buf, LBWriteMode, LBWriteModeEnable);
    }

    if (gmesa->EnabledFlags & GAMMA_BACK_BUFFER)
        PROCESS_DMA_BUFFER_TOP_HALF(gmesa);

    DRM_SPINUNLOCK(&driScrnPriv->pSAREA->drawable_lock,
		       driScrnPriv->drawLockID);
    VALIDATE_DRAWABLE_INFO_NO_LOCK_POST(gmesa);

    if (gmesa->EnabledFlags & GAMMA_BACK_BUFFER)
        PROCESS_DMA_BUFFER_BOTTOM_HALF(gmesa);
  } else {
    _mesa_problem(NULL, "gammaSwapBuffers: drawable has no context!\n");
  }
}

static GLboolean 
gammaMakeCurrent(__DRIcontextPrivate *driContextPriv,
		 __DRIdrawablePrivate *driDrawPriv,
		 __DRIdrawablePrivate *driReadPriv)
{
    if (driContextPriv) {
	GET_CURRENT_CONTEXT(ctx);
	gammaContextPtr oldGammaCtx = ctx ? GAMMA_CONTEXT(ctx) : NULL;
	gammaContextPtr newGammaCtx = (gammaContextPtr) driContextPriv->driverPrivate;

	if ( newGammaCtx != oldGammaCtx ) {
	    newGammaCtx->dirty = ~0;
	}

	if (newGammaCtx->driDrawable != driDrawPriv) {
	    newGammaCtx->driDrawable = driDrawPriv;
	    gammaUpdateWindow ( newGammaCtx->glCtx );
	    gammaUpdateViewportOffset( newGammaCtx->glCtx );
	}

#if 0
	newGammaCtx->Window &= ~W_GIDMask;
	newGammaCtx->Window |= (driDrawPriv->index << 5);
	CHECK_DMA_BUFFER(newGammaCtx,1);
	WRITE(newGammaCtx->buf, GLINTWindow, newGammaCtx->Window);
#endif

newGammaCtx->new_state |= GAMMA_NEW_WINDOW; /* FIXME */

	_mesa_make_current2( newGammaCtx->glCtx, 
			  (GLframebuffer *) driDrawPriv->driverPrivate,
			  (GLframebuffer *) driReadPriv->driverPrivate );
    } else {
	_mesa_make_current( 0, 0 );
    }
    return GL_TRUE;
}


static GLboolean 
gammaUnbindContext( __DRIcontextPrivate *driContextPriv )
{
   return GL_TRUE;
}

const struct __DriverAPIRec driDriverAPI = {
   gammaInitDriver,
   gammaDestroyScreen,
   gammaCreateContext,
   gammaDestroyContext,
   gammaCreateBuffer,
   gammaDestroyBuffer,
   gammaSwapBuffers,
   gammaMakeCurrent,
   gammaUnbindContext
};



/*
 * This is the bootstrap function for the driver.
 * The __driCreateScreen name is the symbol that libGL.so fetches.
 * Return:  pointer to a __DRIscreenPrivate.
 */
void *__driCreateScreen(Display *dpy, int scrn, __DRIscreen *psc,
                        int numConfigs, __GLXvisualConfig *config)
{
   __DRIscreenPrivate *psp;
   psp = __driUtilCreateScreen(dpy, scrn, psc, numConfigs, config, &gammaAPI);
   return (void *) psp;
}
