/**
 ** svga8.c ---- the 256 color (Super) VGA frame driver
 **
 ** Copyright (c) 1995 Csaba Biegl, 820 Stirrup Dr, Nashville, TN 37221
 ** [e-mail: csaba@vuse.vanderbilt.edu].
 **
 ** This file is part of the GRX graphics library.
 **
 ** The GRX graphics library is free software; you can redistribute it
 ** and/or modify it under some conditions; see the "copying.grx" file
 ** for details.
 **
 ** This library is distributed in the hope that it will be useful,
 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 **
 **/

#include "libgrx.h"
#include "grdriver.h"
#include "arith.h"
#include "docolor.h"
#include "mempeek.h"
#include "memfill.h"


/* frame offset address calculation */
#define FOFS(x,y,lo) umuladd32((y),(lo),(x))


static INLINE
GrColor readpixel(GrFrame *c,int x,int y)
{
	GR_int32u offs;
	GRX_ENTER();
	offs = FOFS(x,y,SCRN->gc_lineoffset);
	CHKBANK(BANKNUM(offs));
	setup_far_selector(SCRN->gc_selector);
	GRX_RETURN((GR_int8u)peek_b_f(&SCRN->gc_baseaddr[0][BANKPOS(offs)]));
}


static INLINE
void drawpixel(int x,int y,GrColor color)
{
	GR_int32u offs;
	char *ptr;
	GRX_ENTER();
	offs = FOFS(x,y,CURC->gc_lineoffset);
	ptr  = &CURC->gc_baseaddr[0][BANKPOS(offs)];
	CHKBANK(BANKNUM(offs));
	setup_far_selector(CURC->gc_selector);
	switch(C_OPER(color)) {
	    case C_XOR: poke_b_f_xor(ptr,(GR_int8u)color); break;
	    case C_OR:  poke_b_f_or( ptr,(GR_int8u)color); break;
	    case C_AND: poke_b_f_and(ptr,(GR_int8u)color); break;
	    default:    poke_b_f(    ptr,(GR_int8u)color); break;
	}
	GRX_LEAVE();
}


static void drawhline(int x,int y,int w,GrColor color)
{
	int copr;
	GRX_ENTER();
	copr = C_OPER(color);
	if(DOCOLOR8(color,copr)) {
	  GR_int32u offs = FOFS(x,y,CURC->gc_lineoffset);
	  unsigned int w1 = BANKLFT(offs);
	  unsigned int w2 = w - (w1 = umin(w,w1));
	  GR_repl cval = freplicate_b(color);
	  setup_far_selector(CURC->gc_selector);
	  do {
	    char *pp = &CURC->gc_baseaddr[0][BANKPOS(offs)];
	    CHKBANK(BANKNUM(offs));
	    offs += w1;
	    switch(copr) {
		case C_XOR: repfill_b_f_xor(pp,cval,w1); break;
		case C_OR:  repfill_b_f_or( pp,cval,w1); break;
		case C_AND: repfill_b_f_and(pp,cval,w1); break;
		default:    repfill_b_f(    pp,cval,w1); break;
	    }
	    w1 = w2;
	    w2 = 0;
	  } while(w1 != 0);
	}
	GRX_LEAVE();
}


static void drawvline(int x,int y,int h,GrColor color)
{
	int copr;
	GRX_ENTER();
	copr = C_OPER(color);
	if(DOCOLOR8(color,copr)) {
	  unsigned int lwdt = CURC->gc_lineoffset;
	  GR_int32u offs = FOFS(x,y,lwdt);
	  setup_far_selector(CURC->gc_selector);
	  do {
	    char *pp = &CURC->gc_baseaddr[0][BANKPOS(offs)];
	    unsigned int h1 = BANKLFT(offs) / lwdt;
	    h -= (h1 = umin(h,umax(h1,1)));
	    CHKBANK(BANKNUM(offs));
	    offs += umul32(h1,lwdt);
	    switch(copr) {
		case C_XOR: colfill_b_f_xor(pp,lwdt,(int)color,h1); break;
		case C_OR:  colfill_b_f_or( pp,lwdt,(int)color,h1); break;
		case C_AND: colfill_b_f_and(pp,lwdt,(int)color,h1); break;
		default:    colfill_b_f(    pp,lwdt,(int)color,h1); break;
	    }
	  } while(h != 0);
	}
	GRX_LEAVE();
}


static void drawblock(int x,int y,int w,int h,GrColor color)
{
	int copr;
	GRX_ENTER();
	copr = C_OPER(color);
	if(DOCOLOR8(color,copr)) {
	  unsigned int skip = CURC->gc_lineoffset;
	  GR_int32u offs = FOFS(x,y,skip);
	  GR_repl   cval = freplicate_b(color);
	  skip -= w;
	  setup_far_selector(CURC->gc_selector);
	  do {
	    unsigned int w1 = BANKLFT(offs);
	    unsigned int w2 = w - (w1 = umin(w,w1));
	    do {
		char *pp = &CURC->gc_baseaddr[0][BANKPOS(offs)];
		CHKBANK(BANKNUM(offs));
		offs += w1;
		switch(copr) {
		    case C_XOR: repfill_b_f_xor(pp,cval,w1); break;
		    case C_OR:  repfill_b_f_or( pp,cval,w1); break;
		    case C_AND: repfill_b_f_and(pp,cval,w1); break;
		    default:    repfill_b_f(    pp,cval,w1); break;
		}
		w1 = w2;
		w2 = 0;
	    } while(w1 != 0);
	    offs += skip;
	  } while(--h != 0);
	}
	GRX_LEAVE();
}


#if defined(__DJGPP__)
/* The VGA banked frame buffer must start on a 64k boundary
** for this optimized assembler code.
** Linux: mmap may place the frame buffer on a 4k boundary :(
*/
static void drawline(int x,int y,int dx,int dy,GrColor color)
{
	struct {
	    int errsub;         /* subtract from error term */
	    int erradd;         /* add to error term when carry */
	    int offset1;        /* add to pointer if no carry on error term */
	    int offset2;        /* add to pointer if carry / banking dir */
	} lndata;
	int  copr, xstep, npts,error;
	char *ptr;
	GR_int32u offs;

	GRX_ENTER();

#       ifdef __GNUC__
#       ifdef __i386__
#       define ASM_LINE1(OPC) asm volatile(""                     \
	"   .align 2,0x90                                 \n"     \
	"0: "#OPC"b %6,"I386_GCC_FAR_SELECTOR"(%0)        \n"     \
	"   subl %7,%2                                    \n"     \
	"   jb   1f                                       \n"     \
	"   subw $1,%3                                    \n"     \
	"   jb   2f                                       \n"     \
	"   decl %1                                       \n"     \
	"   jne  0b                                       \n"     \
	"   jmp  4f                                       \n"     \
	"   .align 2,0x90                                 \n"     \
	"1: addl 4  + %7,%2                               \n"     \
	"   addw 12 + %7,%3                               \n"     \
	"   jb   3f                                       \n"     \
	"   decl %1                                       \n"     \
	"   jne  0b                                       \n"     \
	"   jmp  4f                                       \n"     \
	"2: negl 8 + %7                                   \n"     \
	"3: decl %1                                       \n"     \
	"4: "                                                     \
	 :  "=r" (ptr),                "=r" (npts), "=r" (error)  \
	 :  "0"  ((short)(long)(ptr)), "1"  (npts), "2"  (error), \
	    "q"  ((char)(color)),      "o"  (lndata)              \
	)
#       define ASM_LINE2(OPC) asm volatile(""                     \
	"   .align 2,0x90                                 \n"     \
	"0: "#OPC"b %6,"I386_GCC_FAR_SELECTOR"(%0)        \n"     \
	"   subl %7,%2                                    \n"     \
	"   jb   1f                                       \n"     \
	"   addw 8 + %7,%3                                \n"     \
	"   jb   2f                                       \n"     \
	"   decl %1                                       \n"     \
	"   jne  0b                                       \n"     \
	"   jmp  3f                                       \n"     \
	"   .align 2,0x90                                 \n"     \
	"1: addl 4  + %7,%2                               \n"     \
	"   addw 12 + %7,%3                               \n"     \
	"   jb   2f                                       \n"     \
	"   decl %1                                       \n"     \
	"   jne  0b                                       \n"     \
	"   jmp  3f                                       \n"     \
	"2: decl %1                                       \n"     \
	"3: "                                                     \
	 :  "=r" (ptr),                "=r" (npts), "=r" (error)  \
	 :  "0"  ((short)(long)(ptr)), "1"  (npts), "2"  (error), \
	    "q"  ((char)(color)),      "o"  (lndata)              \
	)
#       endif
#       endif

	copr = C_OPER(color);
	while(DOCOLOR8(color,copr)) {
	   if(dy < 0) {
	       y -= (dy = (-dy));
	       x -= (dx = (-dx));
	   }
	   if(dx < 0) {
	       xstep = (-1);
	       dx    = (-dx);
	   } else
	       xstep = 1;

	   offs = FOFS(x,y,CURC->gc_lineoffset);
	   ptr  = &CURC->gc_baseaddr[0][BANKPOS(offs)];
	   CHKBANK(BANKNUM(offs));
	   setup_far_selector(CURC->gc_selector);
	   if(dx > dy) {
	       npts  = dx +  1;
	       error = dx >> 1;
	       lndata.errsub  = dy;
	       lndata.erradd  = dx;
	       lndata.offset1 = xstep;
	       lndata.offset2 = CURC->gc_lineoffset + xstep;
	       if(xstep < 0) {
		 again_nonlinear:
		   lndata.offset1 = 1;
		   switch(copr) {
		       case C_XOR: ASM_LINE1(xor); break;
		       case C_OR:  ASM_LINE1(or);  break;
		       case C_AND: ASM_LINE1(and); break;
		       default:    ASM_LINE1(mov); break;
		   }
		   if(npts == 0) break;
		   SETBANK(DRVINFO->curbank + lndata.offset1);
		   goto again_nonlinear;
	       }
	   }
	   else {
	       npts  = dy +  1;
	       error = dy >> 1;
	       lndata.errsub  = dx;
	       lndata.erradd  = dy;
	       lndata.offset1 = CURC->gc_lineoffset;
	       lndata.offset2 = CURC->gc_lineoffset + xstep;
	   }
	 again_linear:
	   switch(copr) {
	       case C_XOR: ASM_LINE2(xor); break;
	       case C_OR:  ASM_LINE2(or);  break;
	       case C_AND: ASM_LINE2(and); break;
	       default:    ASM_LINE2(mov); break;
	   }
	   if(npts == 0) break;
	   SETBANK(DRVINFO->curbank + 1);
	   goto again_linear;
	}
	GRX_LEAVE();
}
#else
static
#include "fdrivers/generic/line.c"
#endif


static void drawbitmap(int x,int y,int w,int h,char *bmp,int pitch,int start,GrColor fg,GrColor bg)
{
	int fgop, bgop;
	int dofg, dobg;
	GRX_ENTER();
	fgop = C_OPER(fg);
	bgop = C_OPER(bg);
	dofg = DOCOLOR8(fg,fgop);
	dobg = DOCOLOR8(bg,bgop);
	if(dofg | dobg) {
	    unsigned int skip = CURC->gc_lineoffset;
	    GR_int32u offs = FOFS(x,y,skip);
	    int once = dofg && dobg && (fgop == bgop);
	    skip -= w;
	    bmp += start >> 3;
	    start &= 7;
	    setup_far_selector(CURC->gc_selector);
	    do {
		GR_int8u *bp = (GR_int8u *)bmp;
		GR_int8u bits    = *bp;
		GR_int8u mask    = 0x80 >> start;
		unsigned int  w1 = BANKLFT(offs);
		unsigned int  w2 = w - (w1  = umin(w,w1));
		do {
		    char *pp = &CURC->gc_baseaddr[0][BANKPOS(offs)];
		    CHKBANK(BANKNUM(offs));
		    offs += w1;
#                   define DOBOTH(POKEOP) do {                              \
			POKEOP(pp,((bits&mask)?(GR_int8u)fg:(GR_int8u)bg)); \
			if((mask >>= 1) == 0) bits = *++bp,mask = 0x80;     \
			pp++;                                               \
		    } while(--w1 != 0)
#                   define DOFGC(POKEOP) do {                               \
			if((mask & bits) != 0) POKEOP(pp,(GR_int8u)fg);     \
			if((mask >>= 1)  == 0) bits = *++bp,mask = 0x80;    \
			pp++;                                               \
		    } while(--w1 != 0)
#                   define DOBGC(POKEOP) do {                               \
			if((mask & bits) == 0) POKEOP(pp,(GR_int8u)bg);     \
			if((mask >>= 1)  == 0) bits = *++bp,mask = 0x80;    \
			pp++;                                               \
		    } while(--w1 != 0)
		    if(once) switch(fgop) {
			case C_XOR: DOBOTH(poke_b_f_xor); break;
			case C_OR:  DOBOTH(poke_b_f_or);  break;
			case C_AND: DOBOTH(poke_b_f_and); break;
			default:    DOBOTH(poke_b_f);     break;
		    }
		    else {
			char *ppsave = pp;
			unsigned int w1save = w1;
			if(dofg) switch(fgop) {
			    case C_XOR: DOFGC(poke_b_f_xor); break;
			    case C_OR:  DOFGC(poke_b_f_or);  break;
			    case C_AND: DOFGC(poke_b_f_and); break;
			    default:    DOFGC(poke_b_f);     break;
			}
			if(dobg) {
			    pp   = ppsave;
			    w1   = w1save;
			    bp   = (GR_int8u *)bmp;
			    bits = *bp;
			    mask = 0x80 >> start;
			    switch(bgop) {
				case C_XOR: DOBGC(poke_b_f_xor); break;
				case C_OR:  DOBGC(poke_b_f_or);  break;
				case C_AND: DOBGC(poke_b_f_and); break;
				default:    DOBGC(poke_b_f);     break;
			    }
			}
		    }
		    w1 = w2;
		    w2 = 0;
		} while(w1 != 0);
		offs += skip;
		bmp  += pitch;
	    } while(--h != 0);
	}
	GRX_LEAVE();
}


static
#include "fdrivers/generic/pattern.c"


GrFrameDriver _GrFrameDriverSVGA8 = {
    GR_frameSVGA8,              /* frame mode */
    GR_frameRAM8,               /* compatible RAM frame mode */
    TRUE,                       /* onscreen */
    4,                          /* scan line width alignment */
    1,                          /* number of planes */
    8,                          /* bits per pixel */
    8*16*1024L*1024L,           /* max plane size the code can handle */
    NULL,
    readpixel,
    drawpixel,
    drawline,
    drawhline,
    drawvline,
    drawblock,
    drawbitmap,
    drawpattern,
    _GrFrDrvPackedBitBltV2V,
    _GrFrDrvPackedBitBltV2R,
    _GrFrDrvPackedBitBltR2V,
    _GrFrDrvGenericGetIndexedScanline,
    _GrFrDrvGenericPutScanline
};
