/******************************************************************************
 * mach64 Chapter 7 sample code                                               *
 *                                                                            *
 * dbuf.c - This program performs a simple double buffered amination.         *
 * Example code to show double buffered drawing using the system timer.       *
 * Several image "frames" are loaded into off-screen memory so that they      *
 * can be blited to the screen in sequence. Image tearing is avoided by       *
 * using double page buffering. The page being updated is never the page      *
 * being displayed. In this example, the page buffer is controlled by         *
 * waiting for a system timer tick which yields an approximate frame          *
 * rate of 18. A page swap is done every timer tick. Double buffering         *
 * does not require waiting for a specific vertical line or blanking          *
 * before drawing. If the frame is more than approximately half the           *
 * vertical sync frequency, image tearing will occur. In this case, it        *
 * will be necessary to wait for a specific vertical line before drawing.     *
 *                                                                            *
 * Copyright (c) 1994-1998 ATI Technologies Inc.  All rights reserved.        *
 ******************************************************************************/

#include <stdio.h>
#include <i86.h>
#include "..\util\atim64.h"
#include "..\util\defines.h"
#include "..\util\main.h"
#include "..\util\vtga.h"
#include "isr8.h"

#define BACKGROUND_COLOUR   0xFF
#define NUMFRAMES           20


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

void main (int argc, char *argv[])
{
    char filename[20];
    TARGA_HEADER header;
    short width, height;
    short srcx, srcy;
    short savex1, savey1;
    short savex2, savey2;
    short savex, savey;
    short i, j;
    short y_draw;
    short frame;
    short frames;
    short framesperwidth;
    short topline;
    short y_update;
    short update_flag;
    short old_flag;
    short step;
    unsigned long offset;
    point points[NUMFRAMES];
    int reg1;

    printf ("mach64 Chapter 7 sample code\n"
            "\n"
            "dbuf.c\n"
            "This program performs a simple double buffered amination.\n"
            "Example code to show double buffered drawing using the system timer.\n"
            "Several image <frames> are loaded into off-screen memory so that they\n"
            "can be blited to the screen in sequence. Image tearing is avoided by\n"
            "using double page buffering. The page being updated is never the page\n"
            "being displayed. In this example, the page buffer is controlled by\n"
            "waiting for a system timer tick which yields an approximate frame\n"
            "rate of 18. A page swap is done every timer tick. Double buffering\n"
            "does not require waiting for a specific vertical line or blanking\n"
            "before drawing. If the frame is more than approximately half the\n"
            "vertical sync frequency, image tearing will occur. In this case, it\n"
            "will be necessary to wait for a specific vertical line before drawing.\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);
    clear_screen (0, 0, MODE_INFO.xres, MODE_INFO.yres);

    // Make sure we are in an 8bpp mode.

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

    // Ensure enough memory for double buffer. (need 4MB for 1280).

    if ((QUERY_DATA.memory_size < 3) && (MODE_INFO.xres == 1280))
    {
        // Disable accelerator mode and switch back to VGA text mode.
        finish ();
        printf ("DBUF requires 4MB memory for 1280x1024.\n");
        exit (1);
    } // if

    // Ensure enough memory for double buffer. (need 2M for 800).

    if ((QUERY_DATA.memory_size < 2) && (MODE_INFO.xres != 640))
    {
        // Disable accelerator mode and switch back to VGA text mode.
        finish ();
        printf ("DBUF requires 2MB memory for %dx%d.\n",
                MODE_INFO.xres, MODE_INFO.yres);
        exit (1);
    } // if

    // 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, and save area

    width = header.width;
    height = header.height;
    srcx = 0;
    srcy = (MODE_INFO.yres * 2) + height;
    savex1 = 0;
    savey1 = MODE_INFO.yres * 2;
    savex2 = width;
    savey2 = savey1;
    y_draw = (MODE_INFO.yres - height)/2;
    step = 2;

    // Inform ISR where second page begins (QWORD address).

    // Calculate byte address.

    offset = (unsigned long) (MODE_INFO.yres);
    offset = (unsigned long) (offset * MODE_INFO.pitch);
    if (MODE_INFO.bpp == 4)
    {
        offset = (unsigned long) (offset / 2);
    }
    else
    {
        offset = (unsigned long) (offset * (MODE_INFO.bpp / 8));
    } // if

    // Convert byte address to qword address.

    offset = offset / 8;

    // Add current pitch from CRTC_OFF_PITCH to variable.

    offset = offset | (ior32 (CRTC_OFF_PITCH) & 0xFFC00000);
    set_crtc_offset (offset);

    // 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 * 2);

    // Expand scissors.
    wait_for_fifo (4);
    regw (SC_LEFT, 0);
    regw (SC_TOP, 0);
    regw (SC_RIGHT, MODE_INFO.xres - 1);
    regw (SC_BOTTOM, topline - 1);

    // Setup background 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);

    // Copy background (first buffer) to second buffer.
    blit (0, 0, 0, MODE_INFO.yres, MODE_INFO.xres, MODE_INFO.yres);

    // Load source images.
    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 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);

    // Copy starting postions to buffer save areas.

    blit (0, y_draw, savex1, savey1, width, height);
    blit (0, MODE_INFO.yres + y_draw, savex2, savey2, width, height);

    // Setup loop variables.

    i = 0;
    frame = 0;
    update_flag = get_swap_flag ();
    old_flag = update_flag;

    // Enable timer ISR for page swaps each timer tick.

    init_timer_isr ();

    // Main draw loop.

    while (i < (MODE_INFO.xres-width))
    {
        // Synchronize frame updates with ISR.

        while (update_flag == old_flag)
        {
            update_flag = get_swap_flag ();
        } // while
        old_flag = update_flag;

        // Don't allow ISR to swap page while updating frame.
        interrupts_off ();

        // Set update and save variables according to active frame.
        if (update_flag == 1)
        {
            // Display frame 1, update frame 2.
            y_update = MODE_INFO.yres;
            savex = savex2;
            savey = savey2;
        }
        else
        {
            // Display frame 2, update frame 1.
            y_update = 0;
            savex = savex1;
            savey = savey1;
        } // if

        // Restore current frame from last frame (each page lags by 2 frames).

        if (i > step)
        {
            blit (savex, savey, i - (2 * step), y_update + y_draw,
                  width, height);
        } // if

        // Save current frame.
        blit (i, y_update + y_draw, savex, savey, width, height);

        // Update current frame.
        blit (points[frame].x, points[frame].y, i, y_update + y_draw,
              width, height);

        // Allow ISR to swap page.
        interrupts_on ();

        // Increment image position.
        i = i + step;

        // Determine next frame image.
        frame = (frame + 1) % frames;

        if (kbhit ()) break;

    } // while

    // Disable timer ISR.
    cancel_timer_isr ();

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

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

    exit (0);                           // No errors.

} // main
