/******************************************************************************
 * mach64 Chapter 7 sample code                                               *
 *                                                                            *
 * sbufi.c                                                                    *
 * This example code shows how to implement single buffered drawing using     *
 * interrupts. Several image <frames> are loaded into off-screen memory       *
 * so that they can blitted to the screen in sequence. To prevent image       *
 * tearing, a blit is not performed until the display reaches a specific      *
 * vertical line (usually after the lower part of the blit). In this          *
 * example, this is done by setting the VLINE and INT_CNTL registers to       *
 * trigger an interrupt service routine (ISR). For the ISR to function,       *
 * the mach64's interrupt line must be enabled. On ISA and VLB cards,         *
 * this program expects IRQ2 to be enabled.  IRQ2 may be enabled by           *
 * inserting a jumper on the IRQ2 jumper pins.                                *
 *                                                                            *
 * On mach64 PCI cards, the program queries the interrupt pin field (byte     *
 * offset 3Dh) in the PCI configuration space for the mach64 card to          *
 * determine if the interrupt line has been enabled. If this value is         *
 * zero, the interrupt is considered to be disabled. Otherwise, the           *
 * interrupt is assumed to be enabled and the interrupt line field            *
 * (offset 3Ch) is considered to contain a valid interrupt number. The        *
 * program attempts to locate the configuration space for all current         *
 * mach64 PCI devices until it finds the right one (or fails). A table        *
 * of possible device IDs has been supplied for this purpose. An              *
 * mnterrupt jumper may also be used to enable the interrupt line on PCI      *
 * mach64 cards.                                                              *
 *                                                                            *
 * The ISR is chained into the current ISR for the selected interrupt. It     *
 * may fail to function correctly if the IRQ channel is used by other         *
 * hardware or is prevented from functioning.                                 *
 *                                                                            *
 * Note that the blit code is in the ISR instead of the main loop (as is      *
 * the case for the polled example - SBUFP).                                  *
 *                                                                            *
 * The frame rate is determined by the vertical frequency of the display.     *
 *                                                                            *
 * Copyright (c) 1994-1998 ATI Technologies Inc.  All rights reserved.        *
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <dos.h>
#include <i86.h>
#include "..\util\atim64.h"
#include "..\util\defines.h"
#include "..\util\main.h"
#include "..\util\vtga.h"
#include "pci.h"
#include "vint.h"
#include "visr.h"

#define BACKGROUND_COLOUR   0xFF
#define NUMFRAMES           20
#define IRQ                 2
#define PCI_IRQ_LINE        0x3C
#define PCI_IRQ_PIN         0x3D
#define IRQ_TIMEOUT         30


/******************************************************************************
 * Main Program to demonstrate single buffer animation with polling sync.     *
 *  Function: demonstrate single buffer animation with polling sync.          *
 *    Inputs: Arguments for mode spatial and colour resolution                *
 *   Outputs: NONE                                                            *
 ******************************************************************************/

void main (short argc, char *argv[])
{
    char filename[20];
    TARGA_HEADER header;
    short irq_level;
    short irq_pin;
    short width, height;
    short srcx, srcy;
    short savex, savey;
    short i, j;
    short y_draw;
    short update;
    short frame;
    short frames;
    short framesperwidth;
    short topline;
    short step;
    short oldcount;
    unsigned short starttick, endtick;
    unsigned long save_bus_cntl;
    point points[NUMFRAMES];
    PCI_INFO pci_info;
    PCI_DEVICE_INFO pci_device_info;

    // Known device IDs.

    // unsigned pci_device_ID[] = {0x4354, 0x5654, 0x4754, 0x4758, 0x4358};

    printf ("mach64 Chapter 7 sample code\n"
            "\n"
            "sbufi.c\n"
            "This example code shows how to implement single buffered drawing using\n"
            "interrupts. Several image <frames> are loaded into off-screen memory\n"
            "so that they can blitted to the screen in sequence. To prevent image\n"
            "tearing, a blit is not performed until the display reaches a specific\n"
            "vertical line (usually after the lower part of the blit). In this\n"
            "example, this is done by setting the VLINE and INT_CNTL registers to\n"
            "trigger an interrupt service routine (ISR). For the ISR to function,\n"
            "the mach64's interrupt line must be enabled. On ISA and VLB cards,\n"
            "this program expects IRQ2 to be enabled.  IRQ2 may be enabled by\n"
            "inserting a jumper on the IRQ2 jumper pins.\n"
            "\n"
            "On mach64 PCI cards, the program queries the interrupt pin field (byte\n"
            "offset 3Dh) in the PCI configuration space for the mach64 card to\n"
            "determine if the interrupt line has been enabled. If this value is\n"
            "zero, the interrupt is considered to be disabled. Otherwise, the\n"
            "interrupt is assumed to be enabled and the interrupt line field\n"
            "(offset 3Ch) is considered to contain a valid interrupt number. The\n"
            "program attempts to locate the configuration space for all current\n"
            "mach64 PCI devices until it finds the right one (or fails). A table\n"
            "(more)\n");
    getch ();
    printf ("of possible device IDs has been supplied for this purpose. An\n"
            "interrupt jumper may also be used to enable the interrupt line on PCI\n"
            "mach64 cards.\n"
            "\n"
            "The ISR is chained into the current ISR for the selected interrupt. It\n"
            "may fail to function correctly if the IRQ channel is used by other\n"
            "hardware or is prevented from functioning.\n"
            "\n"
            "Note that the blit code is in the ISR instead of the main loop (as is\n"
            "the case for the polled example - SBUFP).\n"
            "The frame rate is determined by the vertical frequency of the display.\n"
            "\n"
            "This program is only available in 8bpp pixel depth.\n"
            "Spatial resolution (640, 800, 1024, 1280, 1600) should be passsed as\n"
            "an argument. Default setting is 640x480 spatial resolution.\n");

    // Batch command to detect the mach64, perform a hardware query, Save old
    // mode information, process mode info arguments, load and set mode, enable
    // aperture, set up palettes, initialize engine to known state, and reset
    // all engine queues.

    start (argc, argv);

    // Make sure we are in 8bpp mode.

    if (MODE_INFO.bpp != 8)
    {
        // Disable accelerator mode and switch back to VGA text mode.
        finish ();
        printf ("SBUFI requires colour depth of 8 bpp.\n");
        exit (1);
    } // if

    // Determine IRQ level to use for ISR.

    if (QUERY_DATA.bus_type == BUS_PCI)
    {
        if (!pci_bios_present (&pci_info))
        {
            finish ();
            printf ("A PCI BIOS has not been detected.\n");
            exit (1);
        } // if

        if (pci_info.present_status != 0)
        {
            finish ();
            printf ("The IFF EDX has not been set properly.\n");
            exit (1);
        } // if

        // Get the PCI number for the current device.

        for (i = 0; i < sizeof (pci_device_ID) / sizeof (unsigned); i++)
        {
            if (pci_find_device (pci_device_ID[i], VENDOR_ID, 0,
                                 &pci_device_info) == SUCCESS) break;
        } // for

        if (i == sizeof (pci_device_ID) / sizeof (unsigned))
        {
            finish ();
            printf ("Could not get PCI device number.\n");
            exit (1);
        } // if

        // Determine if the interrupt line has been enabled.

        if ((irq_pin = pci_read_config_byte (PCI_IRQ_PIN, &pci_device_info)) ==
            BAD_REGISTER_NUMBER)
        {
            finish ();
            printf ("Error reading interrupt pin number.\n");
            exit (1);
        } // if

        if (irq_pin == 0)
        {
            finish ();
            printf ("This graphics card does not have an interrupt enabled.\n");
            exit (1);
        } // if

        // Get the interrupt line level.

        irq_level = pci_read_config_byte (PCI_IRQ_LINE, &pci_device_info);
        if (irq_level == BAD_REGISTER_NUMBER)
        {
            finish ();
            printf ("Error reading interrupt line level.\n");
            exit (1);
        } // if
    }
    else
    {
        irq_level = IRQ;
    } // if
    
    clear_screen (0, 0, MODE_INFO.xres, MODE_INFO.yres);

    // Get targa header information.
    if (get_targa_header ("..\\image\\frame1.tga", &header) != SUCCESS)
    {
        finish ();
        printf ("Error reading targa file header information.\n");
        exit (1);
    } // if

    // Setup image size, source area, save area, and position increment.
    width = header.width & 0xFFFE;
    height = header.height & 0xFFFE;
    srcx = 0;
    srcy = MODE_INFO.yres + height;
    savex = 0;
    savey = MODE_INFO.yres;
    y_draw = (MODE_INFO.yres - height)/2;
    step = 2;

    // Setup blit dimensions for ISR.
    set_blit_info (width, height, srcx, srcy, savex, savey, y_draw, step);
    set_x_pos (0);
    set_image (0, srcx, srcy);

    // Determine how large to expand the scissors.
    frames = NUMFRAMES;
    framesperwidth = MODE_INFO.xres / width;
    topline = frames / framesperwidth;
    if ((topline * framesperwidth) != frames)
    {
        topline++;
    } // if
    topline = ((topline + 1) * height) + MODE_INFO.yres;

    // Expand scissors to include source and save areas.
    wait_for_fifo (4);
    regw (SC_LEFT, 0);
    regw (SC_TOP, 0);
    regw (SC_RIGHT, MODE_INFO.xres - 1);
    regw (SC_BOTTOM, topline - 1);

    // Set background colour the same as the image background.
    wait_for_fifo (1);
    regw (DP_FRGD_CLR, (BACKGROUND_COLOUR));
    draw_rectangle (0, 0, MODE_INFO.xres, MODE_INFO.yres);

    // Load source images into off-screen memory for frame blitting and
    // record their position.
    frame = 0;
    i = 0;
    j = 0;
    while (frame < frames)
    {
        // Record each frame coordinate.
        points[frame].x = srcx + (width * i);
        points[frame].y = srcy + (height * j);

        // Load next frame image into video memory.
        sprintf (filename, "..\\image\\frame%d.tga", frame + 1);
        if (load_targa (filename, points[frame].x, points[frame].y) != SUCCESS)
        {
            finish ();
            printf ("Error loading targa file to memory.\n");
            exit (1);
        } // if

        // Adjust location of frame load coordinate as necessary.
        frame++;
        i++;
        if (i > ((MODE_INFO.xres / header.width) - 1))
        {
            i = 0;
            j++;
        } // if
    } // while

    // Set palette from targa colour table (8 bpp).
    if (header.pixel_depth == 8)
    {
        if (set_targa_palette ("..\\image\\frame1.tga") != SUCCESS)
        {
            finish ();
            printf ("Error reading targa file colour table information.\n");
            exit (1);
        } // if
    } // if

    // Wait for a key to start.
    getch ();

    // Setup engine for blits.
    wait_for_fifo (3);
    regw (DP_SRC, FRGD_SRC_BLIT);
    regw (SRC_CNTL, 0);
    regw (DST_CNTL, DST_Y_TOP_TO_BOTTOM | DST_X_LEFT_TO_RIGHT);

    // Set bus wait to maximum to insure that interrupt clearing occurs.
    save_bus_cntl = ior32 (BUS_CNTL);
    iow32 (BUS_CNTL, save_bus_cntl | 0x0F);

    // Set vline to wait for (to prevent image tearing) and enable.
    if (y_draw < (MODE_INFO.yres - height))
    {
        set_vline (y_draw + height);
    }
    else
    {
        set_vline (MODE_INFO.yres);
    } // if
    enable_vline_int ();

    // Enable ISR and start draw loop.
    init_vline_isr (irq_level);
    update = get_count ();
    oldcount = update;
    i = 0;
    frame = 0;

    while (i < (MODE_INFO.xres - width))
    {
        // Syncronize with ISR (vline trigger) - time out if no response.
        starttick = *((unsigned int *) (DOS_TICK_ADDRESS));
        endtick = starttick;
        while (update == oldcount)
        {
            update = get_count ();
            endtick = *((unsigned int *) (DOS_TICK_ADDRESS));
            if (abs (endtick - starttick) > IRQ_TIMEOUT)
            {
                disable_vline_int ();
                cancel_vline_isr ();
                finish ();
                printf ("IRQ channel %d is not responding.\n", irq_level);
                exit (0);
            } // if
        } // while
        oldcount = update;

        // Cycle through frames.
        set_image (frame, points[frame].x, points[frame].y);
        frame = (frame + 1) % frames;

        // Increment position.
        i = i + step;
        set_x_pos (i);
        if (kbhit ()) break;
    } // while

    // Disable vline interrupts.
    disable_vline_int ();
    cancel_vline_isr ();

    // Restore bus wait states to original value.
    iow32 (BUS_CNTL, save_bus_cntl);

    // Wait for a carriage exit.
    getch ();

    // Batch command to restore old mode.
    finish ();

    exit (0);                           // No errors.

} // main
