/* $XFree86: xc/programs/Xserver/hw/xfree86/xaa/xaaCpyArea.c,v 1.12 2000/09/28 20:47:59 mvojkovi Exp $ */

#include "misc.h"
#include "xf86.h"
#include "xf86_ansic.h"
#include "xf86_OSproc.h"

#include "X.h"
#include "scrnintstr.h"
#include "xf86str.h"
#include "xaa.h"
#include "xaalocal.h"
#include "migc.h"
#include "gcstruct.h"
#include "pixmapstr.h"

/*
  Written mostly by Harm Hanemaayer (H.Hanemaayer@inter.nl.net).
 */


RegionPtr
XAACopyArea(
    DrawablePtr pSrcDrawable,
    DrawablePtr pDstDrawable,
    GC *pGC,
    int srcx, int srcy,
    int width, int height,
    int dstx, int dsty )
{
    XAAInfoRecPtr infoRec = GET_XAAINFORECPTR_FROM_GC(pGC);

    if(pDstDrawable->type == DRAWABLE_WINDOW) {
	if((pSrcDrawable->type == DRAWABLE_WINDOW) ||
		IS_OFFSCREEN_PIXMAP(pSrcDrawable)){
	    if(infoRec->ScreenToScreenBitBlt &&
	     CHECK_ROP(pGC,infoRec->ScreenToScreenBitBltFlags) &&
	     CHECK_ROPSRC(pGC,infoRec->ScreenToScreenBitBltFlags) &&
	     CHECK_PLANEMASK(pGC,infoRec->ScreenToScreenBitBltFlags))
            return (XAABitBlt( pSrcDrawable, pDstDrawable,
		pGC, srcx, srcy, width, height, dstx, dsty,
		XAADoBitBlt, 0L));
	} else {
	    if(infoRec->WritePixmap &&
	     ((pDstDrawable->bitsPerPixel == pSrcDrawable->bitsPerPixel) ||
		((pDstDrawable->bitsPerPixel == 24) &&  
		(pSrcDrawable->bitsPerPixel == 32) &&
		(infoRec->WritePixmapFlags & CONVERT_32BPP_TO_24BPP))) &&
	     CHECK_ROP(pGC,infoRec->WritePixmapFlags) &&
	     CHECK_ROPSRC(pGC,infoRec->WritePixmapFlags) &&
	     CHECK_PLANEMASK(pGC,infoRec->WritePixmapFlags) &&
	     CHECK_NO_GXCOPY(pGC,infoRec->WritePixmapFlags)) 
            return (XAABitBlt( pSrcDrawable, pDstDrawable,
		pGC, srcx, srcy, width, height, dstx, dsty,
		XAADoImageWrite, 0L));
	}
    } else if(IS_OFFSCREEN_PIXMAP(pDstDrawable)){
	if((pSrcDrawable->type == DRAWABLE_WINDOW) ||
		IS_OFFSCREEN_PIXMAP(pSrcDrawable)){
	    if(infoRec->ScreenToScreenBitBlt &&
	     CHECK_ROP(pGC,infoRec->ScreenToScreenBitBltFlags) &&
	     CHECK_ROPSRC(pGC,infoRec->ScreenToScreenBitBltFlags) &&
	     CHECK_PLANEMASK(pGC,infoRec->ScreenToScreenBitBltFlags))
            return (XAABitBlt( pSrcDrawable, pDstDrawable,
		pGC, srcx, srcy, width, height, dstx, dsty,
		XAADoBitBlt, 0L));
	}
    }

    return (XAAFallbackOps.CopyArea(pSrcDrawable, pDstDrawable, pGC,
   	    srcx, srcy, width, height, dstx, dsty));
}


void
XAADoBitBlt(
    DrawablePtr	    pSrc, 
    DrawablePtr	    pDst,
    GC		    *pGC,
    RegionPtr	    prgnDst,
    DDXPointPtr	    pptSrc )
{
    int nbox, careful;
    BoxPtr pbox, pboxTmp, pboxNext, pboxBase, pboxNew1, pboxNew2;
    DDXPointPtr pptTmp, pptNew1, pptNew2;
    int xdir, ydir;
    XAAInfoRecPtr infoRec = GET_XAAINFORECPTR_FROM_GC(pGC);

    /* XXX we have to err on the side of safety when both are windows,
     * because we don't know if IncludeInferiors is being used.
     */
    careful = ((pSrc == pDst) ||
               ((pSrc->type == DRAWABLE_WINDOW) &&
                (pDst->type == DRAWABLE_WINDOW)));

    pbox = REGION_RECTS(prgnDst);
    nbox = REGION_NUM_RECTS(prgnDst);

    pboxNew1 = NULL;
    pptNew1 = NULL;
    pboxNew2 = NULL;
    pptNew2 = NULL;
    if (careful && (pptSrc->y < pbox->y1)) {
        /* walk source botttom to top */
	ydir = -1;

	if (nbox > 1) {
	    /* keep ordering in each band, reverse order of bands */
	    pboxNew1 = (BoxPtr)ALLOCATE_LOCAL(sizeof(BoxRec) * nbox);
	    if(!pboxNew1)
		return;
	    pptNew1 = (DDXPointPtr)ALLOCATE_LOCAL(sizeof(DDXPointRec) * nbox);
	    if(!pptNew1) {
	        DEALLOCATE_LOCAL(pboxNew1);
	        return;
	    }
	    pboxBase = pboxNext = pbox+nbox-1;
	    while (pboxBase >= pbox) {
	        while ((pboxNext >= pbox) &&
		       (pboxBase->y1 == pboxNext->y1))
		    pboxNext--;
	        pboxTmp = pboxNext+1;
	        pptTmp = pptSrc + (pboxTmp - pbox);
	        while (pboxTmp <= pboxBase) {
		    *pboxNew1++ = *pboxTmp++;
		    *pptNew1++ = *pptTmp++;
	        }
	        pboxBase = pboxNext;
	    }
	    pboxNew1 -= nbox;
	    pbox = pboxNew1;
	    pptNew1 -= nbox;
	    pptSrc = pptNew1;
        }
    } else {
	/* walk source top to bottom */
	ydir = 1;
    }

    if (careful && (pptSrc->x < pbox->x1)) {
	/* walk source right to left */
        xdir = -1;

	if (nbox > 1) {
	    /* reverse order of rects in each band */
	    pboxNew2 = (BoxPtr)ALLOCATE_LOCAL(sizeof(BoxRec) * nbox);
	    pptNew2 = (DDXPointPtr)ALLOCATE_LOCAL(sizeof(DDXPointRec) * nbox);
	    if(!pboxNew2 || !pptNew2) {
		if (pptNew2) DEALLOCATE_LOCAL(pptNew2);
		if (pboxNew2) DEALLOCATE_LOCAL(pboxNew2);
		if (pboxNew1) {
		    DEALLOCATE_LOCAL(pptNew1);
		    DEALLOCATE_LOCAL(pboxNew1);
		}
	        return;
	    }
	    pboxBase = pboxNext = pbox;
	    while (pboxBase < pbox+nbox) {
	        while ((pboxNext < pbox+nbox) &&
		       (pboxNext->y1 == pboxBase->y1))
		    pboxNext++;
	        pboxTmp = pboxNext;
	        pptTmp = pptSrc + (pboxTmp - pbox);
	        while (pboxTmp != pboxBase) {
		    *pboxNew2++ = *--pboxTmp;
		    *pptNew2++ = *--pptTmp;
	        }
	        pboxBase = pboxNext;
	    }
	    pboxNew2 -= nbox;
	    pbox = pboxNew2;
	    pptNew2 -= nbox;
	    pptSrc = pptNew2;
	}
    } else {
	/* walk source left to right */
        xdir = 1;
    }

    (*infoRec->ScreenToScreenBitBlt)(infoRec->pScrn, nbox, pptSrc, pbox, 
	xdir, ydir, pGC->alu, pGC->planemask);
 
    if (pboxNew2) {
	DEALLOCATE_LOCAL(pptNew2);
	DEALLOCATE_LOCAL(pboxNew2);
    }
    if (pboxNew1) {
	DEALLOCATE_LOCAL(pptNew1);
	DEALLOCATE_LOCAL(pboxNew1);
    }

}

void
XAADoImageWrite(
    DrawablePtr	    pSrc, 
    DrawablePtr	    pDst,
    GC		    *pGC,
    RegionPtr	    prgnDst,
    DDXPointPtr	    pptSrc )
{
    int srcwidth;
    unsigned char* psrcBase;			/* start of image */
    unsigned char* srcPntr;			/* index into the image */
    BoxPtr pbox = REGION_RECTS(prgnDst);
    int nbox = REGION_NUM_RECTS(prgnDst);
    XAAInfoRecPtr infoRec = GET_XAAINFORECPTR_FROM_GC(pGC);
    int Bpp = pSrc->bitsPerPixel >> 3; 

    psrcBase = (unsigned char *)((PixmapPtr)pSrc)->devPrivate.ptr;
    srcwidth = (int)((PixmapPtr)pSrc)->devKind;

    for(; nbox; pbox++, pptSrc++, nbox--) {
        srcPntr = psrcBase + (pptSrc->y * srcwidth) + (pptSrc->x * Bpp);

	(*infoRec->WritePixmap)(infoRec->pScrn, pbox->x1, pbox->y1, 
		pbox->x2 - pbox->x1, pbox->y2 - pbox->y1, srcPntr, srcwidth,
		pGC->alu, pGC->planemask, -1, pSrc->bitsPerPixel, pSrc->depth);
    }
}


void
XAADoImageRead(
    DrawablePtr	    pSrc, 
    DrawablePtr	    pDst,
    GC		    *pGC,
    RegionPtr	    prgnDst,
    DDXPointPtr	    pptSrc )
{
    int dstwidth;
    unsigned char* pdstBase;			/* start of image */
    unsigned char* dstPntr;			/* index into the image */
    BoxPtr pbox = REGION_RECTS(prgnDst);
    int nbox = REGION_NUM_RECTS(prgnDst);
    XAAInfoRecPtr infoRec = GET_XAAINFORECPTR_FROM_GC(pGC);
    int Bpp = pSrc->bitsPerPixel >> 3;  /* wouldn't get here unless both
                                           src and dst have same bpp */

    pdstBase = (unsigned char *)((PixmapPtr)pDst)->devPrivate.ptr;
    dstwidth = (int)((PixmapPtr)pDst)->devKind;

    for(; nbox; pbox++, pptSrc++, nbox--) {
        dstPntr = pdstBase + (pbox->y1 * dstwidth) + (pbox->x1 * Bpp);

	(*infoRec->ReadPixmap)(infoRec->pScrn, pptSrc->x, pptSrc->y, 
		pbox->x2 - pbox->x1, pbox->y2 - pbox->y1, dstPntr, dstwidth,
		pSrc->bitsPerPixel, pSrc->depth);
    }
}


void 
XAAScreenToScreenBitBlt(
    ScrnInfoPtr pScrn,
    int nbox,
    DDXPointPtr pptSrc,
    BoxPtr pbox,
    int xdir, int ydir,
    int alu,
    unsigned int planemask )
{
    XAAInfoRecPtr infoRec = GET_XAAINFORECPTR_FROM_SCRNINFOPTR(pScrn);
    int dirsetup;

    if ((!(infoRec->CopyAreaFlags & ONLY_TWO_BITBLT_DIRECTIONS)
    || (xdir == ydir)) &&
    (!(infoRec->CopyAreaFlags & ONLY_LEFT_TO_RIGHT_BITBLT)
    || (xdir == 1))) {
        (*infoRec->SetupForScreenToScreenCopy)(pScrn,
            xdir, ydir, alu, planemask, -1);
        for (; nbox; pbox++, pptSrc++, nbox--)
            (*infoRec->SubsequentScreenToScreenCopy)(pScrn,pptSrc->x, pptSrc->y,
                pbox->x1, pbox->y1, pbox->x2 - pbox->x1, pbox->y2 - pbox->y1);
        SET_SYNC_FLAG(infoRec);
        return;
    }

   if (infoRec->CopyAreaFlags & ONLY_LEFT_TO_RIGHT_BITBLT) {
        /*
         * This is the case of a chip that only supports xdir = 1,
         * with ydir = 1 or ydir = -1, but we have xdir = -1.
         */
        (*infoRec->SetupForScreenToScreenCopy)(pScrn,
            1, ydir, alu, planemask, -1);
        for (; nbox; pbox++, pptSrc++, nbox--)
            if (pptSrc->y != pbox->y1 || pptSrc->x >= pbox->x1)
                /* No problem. Do a xdir = 1 blit instead. */
                (*infoRec->SubsequentScreenToScreenCopy)(pScrn,
                    pptSrc->x, pptSrc->y, pbox->x1, pbox->y1,
                    pbox->x2 - pbox->x1, pbox->y2 - pbox->y1);
            else 
            {
                /*
                 * This is the difficult case. Needs striping into
                 * non-overlapping horizontal chunks.
                 */
                int stripeWidth, w, fullStripes, extra, i;
                stripeWidth = 16;
                w = pbox->x2 - pbox->x1;
                if (pbox->x1 - pptSrc->x < stripeWidth)
                    stripeWidth = pbox->x1 - pptSrc->x;
                fullStripes = w / stripeWidth;
                extra = w % stripeWidth;

                /* First, take care of the little bit on the far right */
                if (extra)
                    (*infoRec->SubsequentScreenToScreenCopy)(pScrn,
                        pptSrc->x + fullStripes * stripeWidth, pptSrc->y,
                        pbox->x1 + fullStripes * stripeWidth, pbox->y1,
                        extra, pbox->y2 - pbox->y1);

                /* Now, take care of the rest of the blit */
                for (i = fullStripes - 1; i >= 0; i--)
                    (*infoRec->SubsequentScreenToScreenCopy)(pScrn,
                        pptSrc->x + i * stripeWidth, pptSrc->y,
                        pbox->x1 + i * stripeWidth, pbox->y1,
                        stripeWidth, pbox->y2 - pbox->y1);
            }
        SET_SYNC_FLAG(infoRec);
        return;
    }

    /*
     * Now the case of a chip that only supports xdir = ydir = 1 or
     * xdir = ydir = -1, but we have xdir != ydir.
     */
    dirsetup = 0;	/* No direction set up yet. */
    for (; nbox; pbox++, pptSrc++, nbox--) {
        if (xdir == 1 && pptSrc->y != pbox->y1) {
            /* Do a xdir = ydir = -1 blit instead. */
            if (dirsetup != -1) {
                (*infoRec->SetupForScreenToScreenCopy)(pScrn,
                    -1, -1, alu, planemask, -1);
                dirsetup = -1;
            }
            (*infoRec->SubsequentScreenToScreenCopy)(pScrn,pptSrc->x, pptSrc->y,
                pbox->x1, pbox->y1, pbox->x2 - pbox->x1, pbox->y2 - pbox->y1);
        }
        else if (xdir == -1 && pptSrc->y != pbox->y1) {
            /* Do a xdir = ydir = 1 blit instead. */
            if (dirsetup != 1) {
                (*infoRec->SetupForScreenToScreenCopy)(pScrn,
                    1, 1, alu, planemask, -1);
                dirsetup = 1;
            }
            (*infoRec->SubsequentScreenToScreenCopy)(pScrn,pptSrc->x, pptSrc->y,
                pbox->x1, pbox->y1, pbox->x2 - pbox->x1, pbox->y2 - pbox->y1);
        }
        else
            if (xdir == 1) {
                /*
                 * xdir = 1, ydir = -1.
                 * Perform line-by-line xdir = ydir = 1 blits, going up.
                 */
                int i;
                if (dirsetup != 1) {
                    (*infoRec->SetupForScreenToScreenCopy)(pScrn,
                        1, 1, alu, planemask, -1);
                    dirsetup = 1;
                }
                for (i = pbox->y2 - pbox->y1 - 1; i >= 0; i--)
                    (*infoRec->SubsequentScreenToScreenCopy)(pScrn,
                        pptSrc->x, pptSrc->y + i, pbox->x1, pbox->y1 + i,
                        pbox->x2 - pbox->x1, 1);
            }
            else {
                /*
                 * xdir = -1, ydir = 1.
                 * Perform line-by-line xdir = ydir = -1 blits, going down.
                 */
                int i;
                if (dirsetup != -1) {
                    (*infoRec->SetupForScreenToScreenCopy)(pScrn,
                        -1, -1, alu, planemask, -1);
                    dirsetup = -1;
                }
                for (i = 0; i < pbox->y2 - pbox->y1; i++)
                    (*infoRec->SubsequentScreenToScreenCopy)(pScrn,
                        pptSrc->x, pptSrc->y + i, pbox->x1, pbox->y1 + i,
                        pbox->x2 - pbox->x1, 1);
            }
    } /* next box */
    SET_SYNC_FLAG(infoRec);
}
