/****************************************************************************
 * modetbl.c - Chapter 3 sample code                                        *
 *                                                                          *
 * This program sets a display mode on the Rage 128 by passing a parameter  *
 * table via a BIOS call.                                                   *
 *                                                                          *
 * Copyright (c) 1999 ATI Technologies Inc.  All rights reserved.           *
 ****************************************************************************/
#include <stdio.h>
#include <dos.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#include <conio.h>
#include "..\util\main.h"
#include "..\util\defines.h"
#include "..\util\regdef.h"

// Prototypes
CRTParameterTable R128_ConvertTable (void);

/****************************************************************************
 * Main program for MODETBL.C                                               *
 *  Function: sets a display mode using a parameter table                   *
 *    Inputs: Arguments for mode spatial and colour resolution              *
 *   Outputs: NONE                                                          *
 ****************************************************************************/
void main (int argc, char *argv[])
{
    union REGS r;
    DWORD psize, segment, selector;
    char *data, *framebuffer;
    int x, y;
    CRTParameterTable table;

    get_old_mode ();

    R128_Detect ();
    R128_FindRom ();
    R128_RegTest ();

    process_command_line (argc, argv);

    // Calculate some of the values used later by the engine.
    R128_AdapterInfo.pitch = R128_AdapterInfo.xres/8;
    if (R128_AdapterInfo.bpp == 24)
    {
        R128_AdapterInfo.pitch *= 3;
    }

    R128_AdapterInfo.bytepp = (R128_AdapterInfo.bpp + 1)/8;

    // Display message regarding mode that will be set.
    printf ("About to set %d x %d x %dbpp mode.\n", R128_AdapterInfo.xres,
                                                    R128_AdapterInfo.yres,
                                                    R128_AdapterInfo.bpp);
    printf ("Press any key to continue...\n");
    getch ();


    // We need to allocate some memory for the mode table, so we can
    // pass the BIOS a real mode address.
    psize = 2; // require 28 bytes of memory.
    if (DPMI_allocdosmem( psize, &segment, &selector) == 0)
    {
        /* can't allocate memory for mode table, shut down */
        R128_ShutDown ();
        printf ("\nUnable to allocate system memory for mode table!");
        exit (1);
    }

    memset (&r, 0, sizeof (r));
    r.w.ax = 0xA000;                    // Function 00h: Set Mode.
    r.w.di = 0x0000;                    // Set CRT only

    // Set DX equal to the segment that was allocated.
    // The offset (BX) will be 0.
    r.w.dx = segment;
    r.w.bx = 0;
    data = (char *)(segment << 4);

    // Convert the mode table to a CRT Parameter Table
    table = R128_ConvertTable ();
    // Copy the converted Parameter Table data to the location pointed
    // to by DX:BX
    memcpy (data, &table, sizeof(CRTParameterTable));

    // Set BIOS to load resolution from specified table and set depth to 16bpp
    r.w.cx = 0x8100 | R128_GetBPPValue (R128_AdapterInfo.bpp);

    // Call the BIOS to set the mode.
    int386 (0x10, &r, &r);
    if (r.h.ah)
    {
        // We have encountered an error setting the display mode.
        set_old_mode ();
        printf ("\nError setting display mode %d x %d, %d bpp.",
                R128_AdapterInfo.xres, R128_AdapterInfo.yres,
                R128_AdapterInfo.bpp);
    }

    // fill the screen with blue pixels
    framebuffer = (char *)R128_AdapterInfo.virtual_MEM_BASE;

    for (x=0; x<R128_AdapterInfo.xres; x++)
    {
        for (y=0; y<R128_AdapterInfo.yres; y++)
        {
            switch (R128_AdapterInfo.bpp)
            {
                case 8:     *framebuffer = DARKBLUE;
                            framebuffer+=1;
                            break;

                case 15:
                case 16:    *framebuffer = 0x1F;
                            framebuffer+=1;
                            *framebuffer = 0x00;
                            framebuffer+=1;
                            break;

                case 24:    *framebuffer = 0xFF;
                            framebuffer+=1;
                            *framebuffer = 0x00;
                            framebuffer+=1;
                            *framebuffer = 0x00;
                            framebuffer+=1;
                            break;

                case 32:    *framebuffer = 0xFF;
                            framebuffer+=1;
                            *framebuffer = 0x00;
                            framebuffer+=1;
                            *framebuffer = 0x00;
                            framebuffer+=1;
                            *framebuffer = 0x00;
                            framebuffer+=1;
                            break;
            } // switch
        } // for (y...
    } // for (x...

    getch ();

    /* free the dosmem */
    if (DPMI_freedosmem (selector) == 0)
    {
        printf ("\nCould not deallocate memory!\n");
    }

    set_old_mode ();

} // main


/****************************************************************************
 * R128_ConvertTable (void)                                                 *
 *  Function: creates a CRTParamterTable structure based on the current     *
 *            resolution.  We convert from the CRTCInfoBlock structures     *
 *            used in the NOBIOS examples.  They are found in MODEPARM.C    *
 *            in the UTIL directory.                                        *
 *    Inputs: NONE                                                          *
 *   Outputs: NONE                                                          *
 ****************************************************************************/
CRTParameterTable R128_ConvertTable (void)
{
    CRTParameterTable ptable;
    CRTCInfoBlock *mtable;
    WORD temp;

    // First, determine what CRTCInfoBlock we will be using, based on
    // the current resolution
    switch (R128_AdapterInfo.xres)
    {
        case 320:   mtable = &mode320_60;
                    break;
        case 400:   mtable = &mode400_75;
                    break;
        case 512:   mtable = &mode512;
                    break;
        case 640:   mtable = &mode640_60;
                    break;
        case 720:   mtable = &mode720_60;
                    break;
        case 800:   mtable = &mode800_60;
                    break;
        case 848:   mtable = &mode848_88;
                    break;
        case 864:   mtable = &mode864_60;
                    break;
        case 1024:  mtable = &mode1024_60;
                    break;
        case 1152:  mtable = &mode1152_60;
                    break;
        case 1280:  mtable = &mode1280_60;
                    break;
        case 1600:  mtable = &mode1600_60;
                    break;
        case 1920:  mtable = &mode1920_60;
                    break;
        default:    mtable = &mode640_60;
                    break;
    }

    // Initialize some common values.  We will assume that there is no
    // overscan, and we will set the refresh rate bit mask to 0xFF.
    ptable.video_mode = 0x0000;
    ptable.refresh_bit_mask = 0x00FF;
    ptable.ovr_wid_top__bottom = 0x0000;
    ptable.ovr_clr_g__b = 0x0000;
    ptable.ovr_clr_r = 0x0000;
    ptable.crtc_h_sync_dly__ovr_wid_right__left = 0x0000;

    // Equate those values that map 1:1 between the structures
    ptable.dot_clock = mtable->PixelClock;
    ptable.crtc_v_total = mtable->VertTotal - 1;
    ptable.crtc_v_sync_strt = mtable->VertSyncStart - 1;

    // Convert the H parameters
    ptable.crtc_h_disp__h_total = (((R128_AdapterInfo.xres / 8) - 1) << 8) |
                                  (mtable->HorizTotal/8 - 1);

    temp = (mtable->HorizSyncEnd - mtable->HorizSyncStart)/8;
    ptable.crtc_h_sync_wid__strt = (temp << 8) | (mtable->HorizSyncStart/8);

    // Convert the V parameters
    ptable.crtc_v_disp = R128_AdapterInfo.yres - 1;
    // We must double the V_DISP if we are in double scanned mode.
    if (mtable->CRTCInfoFlags & CI_DBLSCAN)
    {
        ptable.crtc_v_disp *= 2;
    }

    temp = mtable->VertSyncEnd - mtable->VertSyncStart;
    ptable.crtc_h_disp__v_sync_width = temp & 0xFFFF;

    // Set the flags (bits 1:0, interlace (1), and double scan (0)
    ptable.flags = mtable->CRTCInfoFlags & 0x0003;

    return ptable;
} // R128_ConvertTable () ...

