/****************************************************************************
*
*                         The Universal VESA VBE
*
*                   Copyright (C) 1993 Kendall Bennett.
*                           All rights reserved.
*
* Filename:     $RCSfile: test.c $
* Version:      $Revision: 1.2 $
*
* Language:     ANSI C
* Environment:  IBM PC (MS DOS)
*
* Description:  Module containing test code to test the installed ISR. This
*               will be compiled if testing is defined, and will be called
*               while the VBE is installed. After testing the VBE will be
*               de-installed and the program will exit, without leaving the
*               VBE resident in memory.
*
*               This code will test every function in the VBE 1.2 specs.
*
*               You can also compile it as a standalone test program by
*               setting the STANDALONE switch, to test an already installed
*               ISR.
*
*               MUST be compiled in the large memory model.
*
*               WARNING!! When debugging the resident code using the
*                         Borland C++ IDE, do _not_ try to single step
*                         through this C language module. Set breakpoints
*                         in the ISR routines to check the bits you are
*                         looking at, or use Turbo Debugger.
*
* $Id: test.c 1.2 1993/09/23 03:29:51 kjb release $
*
* Revision History:
* -----------------
*
* $Log: test.c $
* Revision 1.2  1993/09/23  03:29:51  kjb
* Added __DATE__ to logon message.
*
* Revision 1.1  1993/09/19  01:26:00  kjb
* Initial revision
*
****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <string.h>
#include <conio.h>
#include "debug.h"
#include "vesavbe.h"

/*----------------------------- Implementation ----------------------------*/

#ifdef  TESTING

/* Table of memory model names */

static char *memModels[] = {
    "Text Mode",
    "CGA Graphics",
    "Hercules Graphics",
    "4-plane planar",
    "Packed Pixel",
    "Non-chain 4, 256 color",
    "Direct Color RGB",
    "Direct Color YUV",
    };

static  long    bytesPerLine;
static  int     maxx,maxy,bankAdjust = 16;
static  int     memory;
static  char    buf[11] = "00000000b";

/* Routine to convert the input value to its binary representation */

char *binary(unsigned value)
{
    unsigned    mask = 0x80;
    int         i;

    for (i = 0; i < 8; i++) {
        buf[i] = value & mask ? '1' : '0';
        mask >>= 1;
        }

    return buf;
}

void setBank(int bank)
/****************************************************************************
*
* Function:     setBank
* Parameters:   bank    - 64k bank number to set
*
* Description:  Calls the VESA BIOS to set a particular bank number.
*
****************************************************************************/
{
    union REGS  regs;

    regs.x.ax = 0x4F05;
    regs.x.bx = 0x0000;
    regs.x.dx = bank * bankAdjust;
    int86(0x10,&regs,&regs);
}

void putPixel(int x,int y,int color)
/****************************************************************************
*
* Function:     putPixel
* Parameters:   x,y     - Coordinate to plot pixel at
*               color   - Color to plot the pixel in
*
* Description:  Plots a pixel in 640x480 256 color video mode.
*
****************************************************************************/
{
    long        address = y * bytesPerLine + x;
    char        *memPtr = FP(0xA000,(address & 0xFFFF));
    static int  oldBank = 0xFF;

    if (oldBank != (address >> 16))
        setBank(oldBank = address >> 16);
    *memPtr = color;
}

void writeText(int x,int y,uchar *str)
/****************************************************************************
*
* 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;
    struct REGPACK  regs;

    regs.r_ax = 0x1130;
    regs.r_bx = 0x0600;
    intr(0x10,&regs);
    _font = FP(regs.r_es,regs.r_bp);

    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,15);
                byte <<= 1;
                }
            }
        x += 8;
        }
}

PUBLIC void line(int x1,int y1,int x2,int y2,int color)
/****************************************************************************
*
* Function:     line
* Parameters:   x1,y1       - First endpoint of line
*               x2,y2       - Second endpoint of line
*
* 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                   */
            }
        }
}

void moireTest(void)
/****************************************************************************
*
* Function:     moireTest
*
* Description:  Draws a simple Moire pattern on the display screen using
*               lines, and waits for a key press.
*
****************************************************************************/
{
    int     i;

    for (i = 0; i < maxx; i += 4) {
        line(maxx/2,maxy/2,i,0,i % 255);
        line(maxx/2,maxy/2,i,maxy,(i+1) % 255);
        }
    for (i = 0; i < maxy; i += 4) {
        line(maxx/2,maxy/2,0,i,(i+2) % 255);
        line(maxx/2,maxy/2,maxx,i,(i+3) % 255);
        }
    line(0,0,maxx,0,15);
    line(0,0,0,maxy,15);
    line(maxx,0,maxx,maxy,15);
    line(0,maxy,maxx,maxy,15);
}

void checkFail(int function,union REGS *regs)
/****************************************************************************
*
* Function:     checkFail
* Parameters:   function    - Function number called
*
* Description:  Verifies that the function completed successfully.
*
****************************************************************************/
{
    if (regs->x.ax != 0x004F) {
        printf("Function %2Xh failed!!\n",function);
        exit(1);
        }
}

void dumpModeInfo(int mode)
/****************************************************************************
*
* Function:     dumpModeInfo
* Parameters:   mode    - Mode number to dump info for
*
* Description:  Dumps the information about the specific mode to the
*               display.
*
****************************************************************************/
{
    ModeInfoBlock   modeInfo;
    union REGS      regs;
    struct SREGS    sregs;

    sregs.es = FP_SEG(&modeInfo);
    regs.x.di = FP_OFF(&modeInfo);
    regs.x.ax = 0x4F01;
    regs.x.cx = mode;
    int86x(0x10,&regs,&regs,&sregs);
    checkFail(1,&regs);
    if ((modeInfo.ModeAttributes & 0x1) == 0)
        return;
    printf("\nMode number:     %04Xh\n",mode);
    printf("ModeAttributes:  %s (%04Xh)\n",binary(modeInfo.ModeAttributes),
        modeInfo.ModeAttributes);
    printf("WinAAttributes:  %s (%04Xh)\n",binary(modeInfo.WinAAttributes),
        modeInfo.WinAAttributes);
    printf("WinBAttributes:  %s (%04Xh)\n",binary(modeInfo.WinBAttributes),
        modeInfo.WinBAttributes);
    printf("WinGranulatiry:  %d\n",modeInfo.WinGranularity);
    printf("WinSize:         %d\n",modeInfo.WinSize);
    printf("WinASegment:     %04Xh\n",modeInfo.WinASegment);
    printf("WinBSegment:     %04Xh\n",modeInfo.WinBSegment);
    printf("WinFuncPtr:      %04X:%04X\n",FP_SEG(modeInfo.WinFuncPtr),
        FP_OFF(modeInfo.WinFuncPtr));
    if (modeInfo.ModeAttributes & 0x10) {
        printf("Resolution:      %d x %d x %d bits per pixel (%02Xh BytesPerLine)\n",
            modeInfo.XResolution,modeInfo.YResolution,modeInfo.BitsPerPixel,
            modeInfo.BytesPerScanLine);
        printf("MemoryModel:     %s",memModels[modeInfo.MemoryModel]);
        if (modeInfo.MemoryModel == 3)
            printf(" (%d planes)",modeInfo.NumberOfPlanes);
        printf("\n");
        printf("CharSize:        %d x %d\n",
            modeInfo.XCharSize,modeInfo.YCharSize);
        if (modeInfo.MemoryModel >= 6) {
            printf("Red Component:   %d bits, position %d\n",
                modeInfo.RedMaskSize,modeInfo.RedFieldPosition);
            printf("Green Component: %d bits, position %d\n",
                modeInfo.GreenMaskSize,modeInfo.GreenFieldPosition);
            printf("Blue Component:  %d bits, position %d\n",
                modeInfo.BlueMaskSize,modeInfo.BlueFieldPosition);
            printf("Rsvd Component:  %d bits, position %d\n",
                modeInfo.RsvdMaskSize,modeInfo.RsvdFieldPosition);
            printf("DirectColorInfo: %s (%d)\n",
                binary(modeInfo.DirectColorModeInfo),
                modeInfo.DirectColorModeInfo);
            }
        }
    else {
        printf("Resolution:      %d x %d Text Mode (%d x %d charsize)\n",
            modeInfo.XResolution,modeInfo.YResolution,
            modeInfo.XCharSize,modeInfo.YCharSize);
        }
    printf("NumberOfPages:   %d\n",modeInfo.NumberOfImagePages+1);
}

void setSuperVGAMode(int mode)
/****************************************************************************
*
* Function:     setSuperVGAMode
* Parameters:   mode    - SuperVGA mode to set
*
* Description:  Sets up the specified video mode, and obtains the correct
*               dimensions and bytesPerLine values for the mode.
*
****************************************************************************/
{
    ModeInfoBlock   modeInfo;
    union REGS      regs;
    struct SREGS    sregs;
    int             i,maxBank;
    void            *videoMem1,*videoMem2;

    sregs.es = FP_SEG(&modeInfo);
    regs.x.di = FP_OFF(&modeInfo);
    regs.x.ax = 0x4F01;
    regs.x.cx = mode;
    int86x(0x10,&regs,&regs,&sregs);
    checkFail(1,&regs);

    regs.x.ax = 0x4F02;             /* Set the mode */
    regs.x.bx = mode;
    int86(0x10,&regs,&regs);
    checkFail(2,&regs);

    bytesPerLine = modeInfo.BytesPerScanLine;
    maxx = modeInfo.XResolution-1;
    maxy = modeInfo.YResolution-1;
    bankAdjust = 64 / modeInfo.WinGranularity;

    /* Clear all of video RAM - sometimes cards do not do this */

    maxBank = memory / 64;
    videoMem1 = FP(0xA000,0);
    videoMem2 = FP(0xA800,0);
    for (i = 0; i < maxBank; i++) {
        setBank(i);
        memset(videoMem1,0,32768);
        memset(videoMem2,0,32768);
        }
}

void setDisplayStart(int x,int y)
/****************************************************************************
*
* Function:     setDisplayStart
* Parameters:   x,y - Position of the first pixel to display
*
* Description:  Sets the new starting display position to implement
*               hardware scrolling.
*
****************************************************************************/
{
    union REGS  regs;

    regs.x.ax = 0x4F07;
    regs.x.bx = 0x0000;
    regs.x.cx = x;
    regs.x.dx = y;
    int86(0x10,&regs,&regs);
    checkFail(7,&regs);
}

bool checkVESAPageFlip(void)
/****************************************************************************
*
* Function:     checkVESAPageFlip
* Returns:      True if VBE supports page flipping.
*
* Description:  Determines if the VESA VBE supports extended page
*               flipping or not.
*
****************************************************************************/
{
    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;
}

void testISR(void)
/****************************************************************************
*
* Function:     testISR
*
* Description:  This routine will be called if testing is turned on
*               during the compilation of the TSR. You can put whatever
*               code you like into this routine, to test the VESA
*               interface on your system.
*
****************************************************************************/
{
    VgaInfoBlock    vgaInfo;
    union REGS      regs;
    struct SREGS    sregs;
	short           x,y,max,scrollx,scrolly,i,*modes;
    int             oldMode,stateSize;
    void            *videoState;
    char            buf[80];

    printf("Function 00h - Return Super VGA information:\n\n");
    sregs.es = FP_SEG(&vgaInfo);
    regs.x.di = FP_OFF(&vgaInfo);
    regs.x.ax = 0x4F00;
    int86x(0x10,&regs,&regs,&sregs);
    checkFail(0,&regs);
    if (strncmp(vgaInfo.VESASignature,"VESA",4) != 0) {
        printf("Invalid signature!!\n");
        exit(1);
        }
    printf("VBE Version:  %d.%d\n",vgaInfo.VESAVersion >> 0x8,
        vgaInfo.VESAVersion & 0xF);
    printf("OEMString:    %s\n",vgaInfo.OEMStringPtr);
    printf("Capabilities: %s (%04Xh)\n",binary(vgaInfo.Capabilities),
        vgaInfo.Capabilities);
    printf("Total Memory: %d Kb\n",memory = vgaInfo.TotalMemory * 64);
    printf("\nAvailable Modes:\n\n");

    modes = vgaInfo.VideoModePtr;
    i = 0;
    while (*modes != -1) {
        printf("%04Xh ",*modes++);
        if ((++i % 10) == 0)
            printf("\n");
        }

    printf("\n\nPress any key.\n");
    getch();

    printf("\nFunction 01h - Return SuperVGA mode information\n\n");

    modes = vgaInfo.VideoModePtr;
    while (*modes != -1) {
        dumpModeInfo(*modes++);
        printf("\nPress any key.\n");
        getch();
        }

    regs.x.ax = 0x4F04;             /* Get size of video state buffer   */
    regs.x.dx = 0x0000;
	regs.x.cx = 0x000F;
    int86(0x10,&regs,&regs);
    checkFail(4,&regs);
    stateSize = regs.x.bx * 64;
    printf("Saving current video state (%d size buffer required)\n",
        stateSize);
    if ((videoState = malloc(stateSize)) == NULL) {
        printf("Out of memory\n");
        exit(1);
        }
    regs.x.ax = 0x4F04;             /* Save current video state         */
    regs.x.dx = 0x0001;
    regs.x.cx = 0x000F;
    regs.x.bx = OFF(videoState);
    sregs.es = SEG(videoState);
    int86x(0x10,&regs,&regs,&sregs);
    checkFail(4,&regs);

    regs.x.ax = 0x4F03;             /* Save the previous video mode     */
    int86(0x10,&regs,&regs);
    checkFail(3,&regs);
    oldMode = regs.x.bx;

    printf("Press a key to initialise 640x480 256 color graphics\n");
    getch();
    setSuperVGAMode(0x101);

    moireTest();
    writeText(20,20,"Function 02h - Set Super VGA video mode");
	writeText(20,40,"640 x 480 256 color graphics mode");
	if (memory >= 512) {
		writeText(20,80,"Function 07h - Set/Get Display Start");
		writeText(20,100,"Press any key to perform vertical scroll test...");
		}
	else
		writeText(20,60,"Press any key to return to text mode...");
	getch();

	if (memory >= 512) {
        if (!checkVESAPageFlip()) {
            writeText(20,140,"VBE does not support extended CRT addressing!");
            writeText(20,160,"Press any key to return to text mode...");
            getch();
            goto noCRT;
			}
		max = (memory*1024L) / bytesPerLine - 480;
		if (max > 480) max = 480;
		for (i = 0; i < max; i++) {         /* Scroll the display up    */
			setDisplayStart(0,i);
			delay(2);
            }

        regs.x.ax = 0x4F07;
        regs.x.bx = 0x0001;
        int86(0x10,&regs,&regs);            /* Get current display start*/
        checkFail(7,&regs);

        for (i = regs.x.dx; i >= 0; i--) {  /* Scroll the display down  */
            setDisplayStart(0,i);
            delay(2);
            }
        writeText(20,120,"Press any key to test wide virtual screen...");
        getch();

        /* Restart the video mode to clear the display, set up a new
         * logical scanline length and attempt horizontal scrolling.
         */

        setSuperVGAMode(0x101);
        regs.x.ax = 0x4F06;
        regs.x.bx = 0x0000;
        regs.x.cx = 1024;           /* Setup for 1024 pixels wide       */
        int86(0x10,&regs,&regs);
        checkFail(6,&regs);
        regs.x.ax = 0x4F06;
        regs.x.bx = 0x0001;
        int86(0x10,&regs,&regs);    /* Read current values              */
        checkFail(6,&regs);
        bytesPerLine = regs.x.bx;
        maxx = regs.x.cx-1;
        maxy = regs.x.dx-1;
        moireTest();
        writeText(20,20,"Function 06h - Set/Get Logical Scan Line Length");
        sprintf(buf,"Virtual buffer now set to %d x %d pixels",maxx+1,maxy+1);
        writeText(20,40,buf);
        writeText(20,60,"Press any key to perform horizontal scroll test...");
        getch();

        scrollx = maxx-639;
        scrolly = maxy-479;
        for (x = y = 0; x <= scrollx; x++) {
            setDisplayStart(x,y);
			delay(2);
            }
        for (x = scrollx,y = 0; y <= scrolly; y++) {
            setDisplayStart(x,y);
            delay(2);
            }
        for (x = scrollx,y = scrolly; x >= 0; x--) {
            setDisplayStart(x,y);
            delay(2);
            }
        for (x = 0,y = scrolly; y >= 0; y--) {
            setDisplayStart(x,y);
            delay(2);
            }
        writeText(20,100,"Press any key to return to text mode");
        getch();
        }

noCRT:
    regs.x.ax = 0x4F04;             /* Restore previous video state     */
    regs.x.dx = 0x0002;
    regs.x.cx = 0x000F;
    regs.x.bx = OFF(videoState);
    sregs.es = SEG(videoState);
    int86x(0x10,&regs,&regs,&sregs);
    checkFail(4,&regs);

    regs.x.ax = 0x4F02;             /* Restore old video mode           */
    regs.x.bx = oldMode;
    int86(0x10,&regs,&regs);
    checkFail(2,&regs);

    printf("Back in text mode\n");
}

#endif

#ifdef  STANDALONE

void main(void)
{
    testISR();
}

#endif
