/****************************************************************************
 * RAGE128 Chapter 6 Sample Code                                            *
 *                                                                          *
 * memmgr.c - Simple offscreen memory manager.								*	
 *                                                                          *
 * Copyright (c) 1999 ATI Technologies Inc.  All rights reserved.           *
 ****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "..\..\util\regdef.h"
#include "..\..\util\defines.h"
#include "..\..\util\main.h"

#define align(a,b) ((((a) + ((b) - 1)) / (b)) * (b))

// buffer information.

typedef struct {
	DWORD Offset;
	DWORD Size;
} BUFFERINFO;

// Buffer pool linked list element.

typedef struct {
	BUFFERINFO BufferInfo;
    struct BUFFERLISTELEM* Prev;
    struct BUFFERLISTELEM* Next;
} BUFFERLISTELEM;

// Buffer pool linked list.

typedef struct {
    BUFFERLISTELEM* First;
    BUFFERLISTELEM* Last;
} BUFFERLIST;

static BUFFERLIST gBufferList = {0};
static DWORD gOffScreenVidMemBaseOffset = 0;


/****************************************************************************
 * SetOffScreenVidMemBaseOffset                                             *
 *  Function: Calculate the base offset of offscreen video memory.          *
 *    Inputs: none                                                          *
 *   Outputs: none                                                          *
 ****************************************************************************/

void SetOffScreenVidMemBaseOffset (void)
{
    DWORD pitch;
    DWORD temp;

	// Get the byte per pixel value.

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

	// Compute scanline pitch for current display mode.
	
    pitch = R128_AdapterInfo.xres * R128_AdapterInfo.bytepp;
                 
	// Set offscreen base offset to region just beyond onscreen memory.

    gOffScreenVidMemBaseOffset = pitch * R128_AdapterInfo.yres;

    temp = align (gOffScreenVidMemBaseOffset, 128);

    gOffScreenVidMemBaseOffset = temp;

}


/****************************************************************************
 * InitBufferList                                                           *
 *  Function: Initialize the memory pool buffer linked list.                *
 *    Inputs: none                                                          *
 *   Outputs: none                                                          *
 ****************************************************************************/

void InitBufferList (void)
{
    // Free the buffer list first incase it has been previously used.

    FreeBufferList();

	gBufferList.First = NULL;
	gBufferList.Last = NULL;

	SetOffScreenVidMemBaseOffset ();
}


/****************************************************************************
 * R128_AllocBuffer															*
 *  Function: Allocate a buffer from the offscreen video memory pool.       *
 *    Inputs: Size - size of buffer in bytes.                               *
 *   Outputs: success: offset of buffer from base of video memory.          *
 *            failure: 0xffffffff                                           *
 ****************************************************************************/

DWORD R128_AllocBuffer (DWORD Size)
{
	BUFFERLISTELEM* pbuflistelem;
	DWORD offset;
    DWORD temp;

	// Allocate a buffer list element.

	pbuflistelem = (BUFFERLISTELEM*) malloc (sizeof (BUFFERLISTELEM));
	if (pbuflistelem == NULL) 
		return (0xffffffff);

	// Compute the new offset based on the offset and size of the last buffer 
	// in the list. If the last element is NULL, this implies that the first 
	// element in the list is alo NULL, and that the list is empty. In this 
	// case, enter the element at the head of the list.

	if (gBufferList.Last)
	{
		// Compute new offset based on offset and size of last buffer.

		offset = gBufferList.Last->BufferInfo.Offset + 
			gBufferList.Last->BufferInfo.Size;

        temp = align (offset, 128);

        offset = temp;

		// Do bounds checking.

		if ((offset + Size) > R128_AdapterInfo.memsize)
		{
			free (pbuflistelem);
			return (0xffffffff);
		}

		// Set the linked list pointer members for this element.

        pbuflistelem->Prev = (struct BUFFERLISTELEM*)gBufferList.Last;
		pbuflistelem->Next = NULL;
		gBufferList.Last = pbuflistelem;
	}
	else
	{
		// This is the first element. Put this buffer at the top
		// of offscreen memory...

		// ... but make sure the offscreen base offset is computed first.

		if (gOffScreenVidMemBaseOffset == 0)
		{
			SetOffScreenVidMemBaseOffset ();
		}

		offset = gOffScreenVidMemBaseOffset;

		// Set the linked list pointer members for this element.

		pbuflistelem->Prev = NULL;
		pbuflistelem->Next = NULL;
		gBufferList.First = pbuflistelem;
		gBufferList.Last = pbuflistelem;
	}

	// Set the offset and size members.

    pbuflistelem->BufferInfo.Offset = offset;
    pbuflistelem->BufferInfo.Size = Size;

	// Return buffer list element pointer as handle.

	return (offset);
}


/****************************************************************************
 * R128_FreeBuffer                                                          *
 *  Function: Free the buffer at the given offset.                          *
 *    Inputs: Offset - offset of buffer returned by R128_AllocBuffer.       *
 *   Outputs: TRUE - successful.                                            *
 *            FALSE - unsuccessful.                                         *
 ****************************************************************************/

BOOL R128_FreeBuffer (DWORD Offset)
{
	BUFFERLISTELEM* pbuflistelem;
	BOOL retval = FALSE;			// Assume failure.
    BUFFERLISTELEM* pelem;

	// Traverse the list from the first to the last element in search
	// of a matching element.

    for (pbuflistelem = gBufferList.First; pbuflistelem != NULL; 
        pbuflistelem = (BUFFERLISTELEM*)pbuflistelem->Next)
	{
		// Found a match. Reassign pointers.

        if (pbuflistelem->BufferInfo.Offset == Offset)
		{
			// If previous is NULL, this is the first element in
			// the list. Update the First member.

			if (pbuflistelem->Prev == NULL)
			{
                gBufferList.First = (BUFFERLISTELEM*)pbuflistelem->Next;
			}
			else
			{
				// Point the previous element's next pointer
				// to this element's next pointer.

                pelem = (BUFFERLISTELEM*)pbuflistelem->Prev;
                pelem->Next = pbuflistelem->Next;
			}

			// If next is NULL, this is the last element in
			// the list. Update the Last member.

			if (pbuflistelem->Next == NULL)
			{
                gBufferList.Last = (BUFFERLISTELEM*)pbuflistelem->Prev;
			}
			else
			{
				// Point the next element's previous pointer
				// to this element's previous pointer.

                pelem = (BUFFERLISTELEM*)pbuflistelem->Next;
                pelem->Prev = pbuflistelem->Prev;
			}

			// Free this list element.

			free (pbuflistelem);

			// Set success return value.

			retval = TRUE;

			break;
		}
	}

	return (retval);
}


/****************************************************************************
 * FreeBufferList                                                           *
 *  Function: Free the entire elements inside the buffer list.              *
 *    Inputs: none                                                          *
 *   Outputs: none                                                          *
 ****************************************************************************/

void FreeBufferList (void)
{

	BUFFERLISTELEM* pbuflistelem;
    BUFFERLISTELEM* pbuflistelem_next;

    // Set pointer to first element in the list.

    pbuflistelem = gBufferList.First;

    // Delete this element, move on to the next.

    while (pbuflistelem != NULL)
    {
        pbuflistelem_next = (BUFFERLISTELEM*)pbuflistelem->Next;
        free (pbuflistelem);
        pbuflistelem = pbuflistelem_next;
    }

    // Zero out the global buffer list structure.

    memset (&gBufferList, 0, sizeof (gBufferList));
}

