/****************************************************************************
*
*                          SuperVGA Test Library
*
*                   Copyright (C) 1993 Kendall Bennett.
*                           All rights reserved.
*
* Filename:     $RCSfile: svgac.c $
* Version:      $Revision: 1.2 $
*
* Language:     ANSI C
* Environment:  IBM PC (MSDOS)
*
* Description:  Simple library to collect together the functions in the
*               SuperVGA test library for use in other C programs. The
*               support is reasonably low level, so you can do what you
*               want. The set of routines in this source file are general
*               SuperVGA routines and are independant of the video mode
*               selected.
*
*               MUST be compiled in the large model.
*
* $Id: svgac.c 1.2 1993/09/23 03:31:45 kjb release $
*
* Revision History:
* -----------------
*
* $Log: svgac.c $
* Revision 1.2  1993/09/23  03:31:45  kjb
* Fixed so it works on Toshiba Laptops...
*
* Revision 1.1  1993/09/19  02:05:28  kjb
* Initial revision
*
****************************************************************************/

#include <string.h>
#include <dos.h>
#include <stdlib.h>
#include "svga.h"
#include "univbe\vesavbe.h"

/*---------------------------- Global Variables ---------------------------*/

#define MAXMODES    50              /* Maximum modes available in list  */

int     maxx,maxy,memory;
int     maxpage,bytesperline;
bool    twobanks = false,extendedflipping = false,widedac = false;
short   modeList[MAXMODES];
char    OEMString[80];

int     oldMode;                    /* Old video mode number            */
bool    old50Lines;                 /* Was old mode 80x50?              */
int     curBank;                    /* Current read/write bank          */
int     bankAdjust;                 /* Bank granularity adjust factor   */
long    pagesize;                   /* Page size for current mode       */
void    *bankSwitch;                /* Pointer to bank switch routine   */
void    *writeBank;                 /* Relocated write bank routine     */
void    *readBank;                  /* Relocated read bank routine      */
void    *pageFlip;                  /* Relocated page flip routine      */

/*----------------------------- Implementation ----------------------------*/

PRIVATE bool checkVESAPageFlip(void)
/****************************************************************************
*
* Function:     checkVESAPageFlip
* Returns:      True if VBE supports page flipping.
*
* Description:  Determines if the VESA VBE supports extended page
*               flipping or not. Assume a suitable video mode has already
*               been initialised.
*
****************************************************************************/
{
    union REGS  regs;

    regs.x.ax = 0x4F07;         /* Set display start service            */
    regs.x.bx = 0;              /* BH := 0, BL := 0 (set display start) */
    regs.x.cx = 0;              /* Leftmost pixel in line               */
    regs.x.dx = 0;              /* First displayed scanline             */
    int86(0x10,&regs,&regs);
    if (regs.x.ax != 0x004F)
        return false;           /* Function failed, not page flip       */

    regs.x.ax = 0x4F07;         /* Get display start service            */
    regs.x.bx = 1;              /* BH := 0, BL := 1 (get display start) */
    int86(0x10,&regs,&regs);
    if (regs.x.ax != 0x004F)
        return false;           /* Function failed, not page flip       */
    if (regs.h.bh != 0)
        return false;
    if (regs.x.cx != 0)
        return false;
    if (regs.x.dx != 0)
        return false;
    return true;
}

bool checkWideDAC(void)
/****************************************************************************
*
* Function:     checkWideDAC
* Returns:      True if 8 bit wide DAC is supported.
*
* Description:  Tests to see if the VBE BIOS supports an 8 bit wide
*               DAC. This assumes the video card is in an appropriate
*               video mode before being called.
*
****************************************************************************/
{
    union REGS  regs;
    short       bits;

    regs.x.ax = 0x4F08;         /* Set DAC service                      */
    regs.x.bx = 0x0800;         /* BH := 8, BL := 0 (set DAC width)     */
    int86(0x10,&regs,&regs);
    if (regs.x.ax != 0x004F)
        return false;           /* Function failed, no wide dac         */
    if (regs.h.bh == 6)
        return false;
    regs.x.ax = 0x4F08;
    regs.x.bx = 0x0001;         /* Get DAC width (should now be 8)      */
    int86(0x10,&regs,&regs);
    if (regs.x.ax != 0x004F)
        return false;
    bits = regs.h.bh;
    regs.x.ax = 0x4F08;
    regs.x.bx = 0x0600;
    int86(0x10,&regs,&regs);    /* Restore to 6 bit DAC                 */
    if (regs.x.ax != 0x004F)
        return false;

    return (bits == 8);
}

PUBLIC int initSuperVGA(void)
/****************************************************************************
*
* Function:     initSuperVGA
* Returns:      VBE version number for the SuperVGA (0 if no SuperVGA).
*
* Description:  Detects if a VESA VBE compliant SuperVGA is out there, and
*               initialises the library if one is. The VBE version number
*               is specified with the major version number in the high
*               byte and the minor version number in the low byte. So
*               version 1.2 is the number 0x102.
*
****************************************************************************/
{
    VgaInfoBlock    vgaInfo;
    ModeInfoBlock   modeInfo;
    union REGS      regs;
    struct SREGS    sregs;
    short           *p,i;

    sregs.es = SEG(&vgaInfo);
    regs.x.di = OFF(&vgaInfo);
    regs.x.ax = 0x4F00;
    int86x(0x10,&regs,&regs,&sregs);    /* Get SuperVGA information     */
    if (regs.x.ax != 0x004F)
        return false;
    if (strncmp(vgaInfo.VESASignature,"VESA",4) != 0)
        return false;

    /* Copy relavent information from the mode block into our globals.
     * Note that the video mode list _may_ be built in the information
     * block that we have passed, so we _must_ copy this from here
     * into our our storage if we want to continue to use it. Note
     * that we filter out the mode 0x6A, which some BIOSes include as
     * well as the 0x102 mode for 800x600x16.
     */

    for (i = 0,p = vgaInfo.VideoModePtr; *p != -1; p++,i++) {
        if (*p != 0x6A)
            modeList[i] = *p;
        }
    modeList[i] = -1;
    memory = vgaInfo.TotalMemory * 64;
    strcpy(OEMString,vgaInfo.OEMStringPtr);

    /* Determine if the board supports separate read/write banks and
     * extended page flipping. Some VESA VBE's require the card to be
     * in a graphics mode for these tests to work, so we find a suitable
     * mode and use that.
     */

    for (p = modeList; *p != -1; p++) {
        sregs.es = SEG(&modeInfo);
        regs.x.di = OFF(&modeInfo);
        regs.x.ax = 0x4F01;
        regs.x.cx = *p;
        int86x(0x10,&regs,&regs,&sregs);    /* Get SuperVGA mode info   */
		if (regs.x.ax == 0x004F &&
				(modeInfo.MemoryModel == 3 || modeInfo.MemoryModel == 4)) {
            modeInfo.WinBAttributes &= 0x7;
            twobanks = (modeInfo.WinBAttributes == 0x3);
            setSuperVGAMode(*p);
            extendedflipping = checkVESAPageFlip();
            widedac = checkWideDAC();
            restoreMode();
            break;
            }
        }

    return vgaInfo.VESAVersion;
}

PRIVATE void computePageInfo(ModeInfoBlock *modeInfo,int *maxpage,
    long *pagesize)
/****************************************************************************
*
* Function:     computePageInfo
* Parameters:   modeInfo    - Pointer to valid mode information block
*               maxpage     - Number of display pages - 1
*               pagesize    - Size of each logical display page in bytes
*
* Description:  Computes the number of image pages and size of each image
*               page for a specified video mode.
*
****************************************************************************/
{
    long    memsize,size;

    if (extendedflipping)
        memsize = memory * 1024L;
    else
        memsize = 256 * 1024L;      /* Use only 256k to compute pages   */

    size = (long)modeInfo->BytesPerScanLine * (long)modeInfo->YResolution;
    if (modeInfo->BitsPerPixel == 4) {
        /* We have a 16 color video mode, so round up the page size to
         * 8k, 16k, 32k or 64k boundaries depending on how large it is.
         */

        size = (size + 0x1FFFL) & 0xFFFFE000L;
        if (size != 0x2000) {
            size = (size + 0x3FFFL) & 0xFFFFC000L;
            if (size != 0x4000) {
                size = (size + 0x7FFFL) & 0xFFFF8000L;
                if (size != 0x8000)
                    size = (size + 0xFFFFL) & 0xFFFF0000L;
                }
            }
        }
    else
        size = (size + 0xFFFFL) & 0xFFFF0000L;

    if (modeInfo->MemoryModel == memPL)
        memsize /= 4;
    if (size <= memsize)
        *maxpage = (memsize / size) - 1;
    else
        *maxpage = 0;
    *pagesize = size;
}

PUBLIC bool getSuperVGAModeInfo(int mode,int *xres,int *yres,
    int *bytesperline,int *bitsperpixel,int *memmodel,int *maxpage,
    long *pagesize)
/****************************************************************************
*
* Function:     getSuperVGAModeInfo
* Parameters:   mode            - Mode to get information about
*               xres            - Place to store x resolution
*               yres            - Place to store y resolution
*               bytesperline    - Bytes per scanline
*               bitsperpixel    - Place to store bits per pixel (2^n colors)
*               memmodel        - Memory model for mode (planar, packed etc)
*               maxpage         - Number of display pages - 1
*               pagesize        - Size of each logical display page in bytes
* Returns:      True if mode number was valid, false if not.
*
* Description:  Obtains information about a specific video mode from the
*               VBE. You should use this function to find the video mode
*               you wish to set, as the new VBE 2.0 mode numbers may be
*               completely arbitrary.
*
****************************************************************************/
{
    ModeInfoBlock   modeInfo;
    union REGS      regs;
    struct SREGS    sregs;

    if (mode <= 0x13) {
        /* This is a standard VGA mode, so fill in the required information
         * ourselves.
         */

        switch (mode) {
            case 0x0D:
                modeInfo.XResolution = 320;
                modeInfo.YResolution = 200;
                modeInfo.BytesPerScanLine = 40;
                modeInfo.BitsPerPixel = 4;
                modeInfo.MemoryModel = memPL;
                break;
            case 0x0E:
                modeInfo.XResolution = 640;
                modeInfo.YResolution = 200;
                modeInfo.BytesPerScanLine = 80;
                modeInfo.BitsPerPixel = 4;
                modeInfo.MemoryModel = memPL;
                break;
            case 0x10:
                modeInfo.XResolution = 640;
                modeInfo.YResolution = 350;
                modeInfo.BytesPerScanLine = 80;
                modeInfo.BitsPerPixel = 4;
                modeInfo.MemoryModel = memPL;
                break;
            case 0x12:
                modeInfo.XResolution = 640;
                modeInfo.YResolution = 480;
                modeInfo.BytesPerScanLine = 80;
                modeInfo.BitsPerPixel = 4;
                modeInfo.MemoryModel = memPL;
                break;
            case 0x13:
                modeInfo.XResolution = 320;
                modeInfo.YResolution = 200;
                modeInfo.BytesPerScanLine = 320;
                modeInfo.BitsPerPixel = 8;
                modeInfo.MemoryModel = memPK;
                break;
            default:
                return false;
            }
        }
    else {
        /* This is a VESA mode, so call the BIOS to get information about
         * it.
         */

        sregs.es = SEG(&modeInfo);
        regs.x.di = OFF(&modeInfo);
        regs.x.ax = 0x4F01;
        regs.x.cx = mode;
        int86x(0x10,&regs,&regs,&sregs);    /* Get mode information         */
        if (regs.x.ax != 0x004F)
            return false;
        if ((modeInfo.ModeAttributes & 0x1) == 0)
            return false;
        }
    *xres = modeInfo.XResolution;
    *yres = modeInfo.YResolution;
    *bytesperline = modeInfo.BytesPerScanLine;
    *memmodel = modeInfo.MemoryModel;
    *bitsperpixel = modeInfo.BitsPerPixel;
    if (*memmodel == memRGB) {
        /* Convert the 32k direct color modes of VBE 1.2+ BIOSes to
         * be recognised as 15 bits per pixel modes.
         */

        if (*bitsperpixel == 16 && modeInfo.RsvdMaskSize == 1)
            *bitsperpixel = 15;
        }
    if (mode == 0x13 && !extendedflipping)
        *maxpage = 0;
    else
        computePageInfo(&modeInfo,maxpage,pagesize);
    return true;
}

typedef struct {
    short   writeBankLen;
    void    *writeBank;
    short   readBankLen;
    void    *readBank;
    short   newPageLen;
    void    *newPage;
    } PMInfoBlock;

PUBLIC bool setSuperVGAMode(int mode)
/****************************************************************************
*
* Function:     setSuperVGAMode
* Parameters:   mode    - SuperVGA video mode to set.
* Returns:      True if the mode was set, false if not.
*
* Description:  Attempts to set the specified video mode. This routine
*               assumes that the library and SuperVGA have been initialised
*               with the initSuperVGA() routine first.
*
****************************************************************************/
{
    ModeInfoBlock   modeInfo;
    PMInfoBlock     *pmInfo = (PMInfoBlock*)&modeInfo;
    union REGS      regs;
    struct SREGS    sregs;
    int             bitsperpixel,memmodel;

    regs.x.ax = 0x0F00;
    int86(0x10,&regs,&regs);
    oldMode = regs.x.ax & 0x7F;         /* Save old video mode          */
    old50Lines = false;                 /* Default to 25 line mode      */
    if (oldMode == 0x3) {
        regs.x.ax = 0x1130;
        regs.x.bx = 0;
        regs.x.dx = 0;
        int86(0x10,&regs,&regs);
        old50Lines = (regs.h.dl == 49);
        }

    regs.x.ax = 0x4F02;
    regs.x.bx = mode;
    int86(0x10,&regs,&regs);            /* Set the video mode           */
    if (regs.x.ax != 0x004F)
        return false;

    getSuperVGAModeInfo(mode,&maxx,&maxy,&bytesperline,&bitsperpixel,
        &memmodel,&maxpage,&pagesize);
    maxx--; maxy--;

    if (mode <= 0x13) {
        /* This is a normal VGA style mode, so we need to determine the
         * correct information for bank switching from the BIOS
         */

        if (mode == 0x13)
            mode = 0x101;
        else
            mode = 0x102;
        }
    sregs.es = SEG(&modeInfo);
    regs.x.di = OFF(&modeInfo);
    regs.x.ax = 0x4F01;
    regs.x.cx = mode;
    int86x(0x10,&regs,&regs,&sregs);    /* Get mode information         */
    bankAdjust = 64 / modeInfo.WinGranularity;
    curBank = -1;
    bankSwitch = modeInfo.WinFuncPtr;

    /* Now set up the vectors to the appropriate bank switching routines.
     * If the Universal VESA VBE is installed, we can move the bank
     * switching routines from there into our own code space for speed
     * (especially under protected mode).
     */

    sregs.es = SEG(&modeInfo);
    regs.x.di = OFF(&modeInfo);
    regs.x.ax = 0x4F01;
    regs.x.cx = -1;
    int86x(0x10,&regs,&regs,&sregs);    /* Get mode information         */
    writeBank = readBank = pageFlip = NULL;
    if (regs.x.ax == 0x004F && regs.x.cx == 0xCABD) {
        /* The Universal VESA VBE is there and functioning, so copy the
         * routines onto the heap and execute from there.
         */

        writeBank = malloc(pmInfo->writeBankLen);
        memcpy(writeBank,pmInfo->writeBank,pmInfo->writeBankLen);
        if (pmInfo->readBankLen > 0) {
            readBank = malloc(pmInfo->readBankLen);
            memcpy(readBank,pmInfo->readBank,pmInfo->readBankLen);
            }
        pageFlip = malloc(pmInfo->newPageLen);
        memcpy(pageFlip,pmInfo->newPage,pmInfo->newPageLen);
        }

    return true;
}

PUBLIC void restoreMode(void)
/****************************************************************************
*
* Function:     restoreMode
*
* Description:  Restore the previous video mode in use before the SuperVGA
*               mode was set. This routine will also restore the 50 line
*               display mode if this mode was previously set.
*
****************************************************************************/
{
    union REGS  regs;

    regs.x.ax = oldMode;
    int86(0x10,&regs,&regs);            /* Set the old video mode       */
    if (old50Lines) {
        regs.x.ax = 0x1112;
        regs.x.bx = 0;
        int86(0x10,&regs,&regs);        /* Restore 50 line mode         */
        }
}

PUBLIC void line(int x1,int y1,int x2,int y2,long color)
/****************************************************************************
*
* Function:     line
* Parameters:   x1,y1       - First endpoint of line
*               x2,y2       - Second endpoint of line
*               color       - Color to draw the line in
*
* Description:  Scan convert a line segment using the MidPoint Digital
*               Differential Analyser algorithm.
*
****************************************************************************/
{
    int     d;                      /* Decision variable                */
    int     dx,dy;                  /* Dx and Dy values for the line    */
    int     Eincr,NEincr;           /* Decision variable increments     */
    int     yincr;                  /* Increment for y values           */
    int     t;                      /* Counters etc.                    */

    dx = ABS(x2 - x1);
    dy = ABS(y2 - y1);

    if (dy <= dx) {

        /* We have a line with a slope between -1 and 1
         *
         * Ensure that we are always scan converting the line from left to
         * right to ensure that we produce the same line from P1 to P0 as the
         * line from P0 to P1.
         */

        if (x2 < x1) {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
            }

        if (y2 > y1)
            yincr = 1;
        else
            yincr = -1;

        d = 2*dy - dx;              /* Initial decision variable value  */
        Eincr = 2*dy;               /* Increment to move to E pixel     */
        NEincr = 2*(dy - dx);       /* Increment to move to NE pixel    */

        putPixel(x1,y1,color);      /* Draw the first point at (x1,y1)  */

        /* Incrementally determine the positions of the remaining pixels
         */

        for (x1++; x1 <= x2; x1++) {
            if (d < 0) {
                d += Eincr;         /* Choose the Eastern Pixel         */
                }
            else {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                y1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
                }
            putPixel(x1,y1,color);  /* Draw the point                   */
            }
        }
    else {

        /* We have a line with a slope between -1 and 1 (ie: includes
         * vertical lines). We must swap our x and y coordinates for this.
         *
         * Ensure that we are always scan converting the line from left to
         * right to ensure that we produce the same line from P1 to P0 as the
         * line from P0 to P1.
         */

        if (y2 < y1) {
            t = x2; x2 = x1; x1 = t;    /* Swap X coordinates           */
            t = y2; y2 = y1; y1 = t;    /* Swap Y coordinates           */
            }

        if (x2 > x1)
            yincr = 1;
        else
            yincr = -1;

        d = 2*dx - dy;              /* Initial decision variable value  */
        Eincr = 2*dx;               /* Increment to move to E pixel     */
        NEincr = 2*(dx - dy);       /* Increment to move to NE pixel    */

        putPixel(x1,y1,color);      /* Draw the first point at (x1,y1)  */

        /* Incrementally determine the positions of the remaining pixels
         */

        for (y1++; y1 <= y2; y1++) {
            if (d < 0) {
                d += Eincr;         /* Choose the Eastern Pixel         */
                }
            else {
                d += NEincr;        /* Choose the North Eastern Pixel   */
                x1 += yincr;        /* (or SE pixel for dx/dy < 0!)     */
                }
            putPixel(x1,y1,color);  /* Draw the point                   */
            }
        }
}

PUBLIC void writeText(int x,int y,uchar *str,long color)
/****************************************************************************
*
* Function:     writeText
* Parameters:   x,y     - Position to begin drawing string at
*               str     - String to draw
*
* Description:  Draws a string using the BIOS 8x16 video font by plotting
*               each pixel in the characters individually. This should
*               work for all video modes.
*
****************************************************************************/
{
    uchar           byte;
    int             i,j,k,length,ch;
    uchar           *font;

    font = getFontVec();
    length = strlen(str);
    for (k = 0; k < length; k++) {
        ch = str[k];
        for (j = 0; j < 16; j++) {
            byte = *(font + ch * 16 + j);
            for (i = 0; i < 8; i++) {
                if ((byte & 0x80) != 0)
                    putPixel(x+i,y+j,color);
                byte <<= 1;
                }
            }
        x += 8;
        }
}
