/*
** Copyright 1991, 1992, Silicon Graphics, Inc.
** All Rights Reserved.
**
** This is UNPUBLISHED PROPRIETARY SOURCE CODE of Silicon Graphics, Inc.;
** the contents of this file may not be disclosed to third parties, copied or
** duplicated in any form, in whole or in part, without the prior written
** permission of Silicon Graphics, Inc.
**
** RESTRICTED RIGHTS LEGEND:
** Use, duplication or disclosure by the Government is subject to restrictions
** as set forth in subdivision (c)(1)(ii) of the Rights in Technical Data
** and Computer Software clause at DFARS 252.227-7013, and/or in similar or
** successor clauses in the FAR, DOD or NASA FAR Supplement. Unpublished -
** rights reserved under the Copyright Laws of the United States.
*/

#include "precomp.h"
#pragma hdrstop

#if DBG

// Set glRandomMallocFail to a positive value, say 40, to enable random
// allocation failures.  The failure will occur every glRandomMallocFail
// times.
long glRandomMallocFail = 0;
static long glRandomFailCount;

// glSize is the size of memory in use.
ULONG glSize = 0;
ULONG glHighWater = 0;
ULONG glReal = 0;

static void AdjustSizes(LONG delta, void *mem)
{
    ULONG nbytes;

#ifdef GL_REAL_SIZE
    nbytes = HeapSize(GetProcessHeap(), 0, mem);
#else
    nbytes = 0;
#endif
    
    if (delta < 0)
    {
        glSize -= (ULONG)(-delta);
        glReal -= nbytes;
        
        if ((int) glSize < 0)
        {
            DBGPRINT("glSize underflows\n");
        }
    }
    else if (delta > 0)
    {
        glSize += delta;
        glReal += nbytes;
        
        if ((int) glSize < 0)
        {
            DBGPRINT("glSize overflows\n");
        }
        
        if (glSize > glHighWater)
        {
#ifdef GL_SHOW_HIGH_WATER
            DbgPrint("glSize high %8d (%8d)\n", glSize, glReal);
#endif
            glHighWater = glSize;
        }
    }
}

typedef struct _MEM_HDR
{
    ULONG nbytes;
    ULONG signature[3];
} MEM_HDR;

// 'GLal' in byte order
#define MEM_ALLOC_SIG 0x6C614C47
// 'GLfr' in byte order
#define MEM_FREE_SIG 0x72664C47

#define MEM_HDR_SIZE sizeof(MEM_HDR)
#define MEM_HDR_PTR(mem) ((MEM_HDR *)((BYTE *)(mem)-MEM_HDR_SIZE))

// XXX We may want to protect these debug allocation functions with a
// critical section.
void * FASTCALL
dbgAlloc(UINT nbytes, DWORD flags)
{
    PVOID mem;

    // If random failure is enabled, fail this call randomly.

    if (glRandomMallocFail)
    {
        if (++glRandomFailCount >= glRandomMallocFail)
        {
            DBGPRINT("dbgAlloc random failing\n");
            glRandomFailCount = 0;
            return NULL;
        }
    }

    if (nbytes == 0)
    {
        DBGERROR("nbytes == 0\n");
        return NULL;
    }
    
    // Allocate extra bytes for debug house keeping.

    mem = HeapAlloc(GetProcessHeap(), flags, nbytes+MEM_HDR_SIZE);

    // Do house keeping and add allocation size so far.

    if (mem)
    {
        MEM_HDR *pmh = (MEM_HDR *)mem;

        pmh->nbytes = nbytes;
        pmh->signature[0] = MEM_ALLOC_SIG;
        pmh->signature[1] = MEM_ALLOC_SIG;
        pmh->signature[2] = MEM_ALLOC_SIG;
        AdjustSizes((LONG)nbytes, mem);
        mem = (PVOID) (pmh+1);
    }
    else
    {
        DBGLEVEL1(LEVEL_ERROR, "dbgAlloc could not allocate %u bytes\n",
                  nbytes);
    }

    DBGLEVEL2(LEVEL_ALLOC, "dbgAlloc of %u returned 0x%x\n", nbytes, mem);
    
    return mem;
}

void FASTCALL
dbgFree(void *mem)
{
    MEM_HDR *pmh;
    
    if (!mem)
    {
#ifdef FREE_OF_NULL_ERR
	// Freeing NULL happens currently so this error results
	// in a little too much spew.
        DBGERROR("mem is NULL\n");
#endif
        return;
    }

    // Verify that the signature is not corrupted.

    pmh = MEM_HDR_PTR(mem);
    if (pmh->signature[0] != MEM_ALLOC_SIG ||
        pmh->signature[1] != MEM_ALLOC_SIG ||
        pmh->signature[2] != MEM_ALLOC_SIG)
    {
        WARNING("Possible memory corruption\n");
    }

    // Make sure it is freed once only.

    pmh->signature[0] = MEM_FREE_SIG;
    pmh->signature[1] = MEM_FREE_SIG;
    pmh->signature[2] = MEM_FREE_SIG;

    // Subtract the allocation size.

    AdjustSizes(-(LONG)pmh->nbytes, pmh);

    HeapFree(GetProcessHeap(), 0, pmh);
    
    DBGLEVEL1(LEVEL_ALLOC, "dbgFree of 0x%x\n", mem);
}

void * FASTCALL
dbgRealloc(void *mem, UINT nbytes)
{
    PVOID memNew;
    MEM_HDR *pmh;

    // If random failure is enabled, fail this call randomly.

    if (glRandomMallocFail)
    {
        if (++glRandomFailCount >= glRandomMallocFail)
        {
            DBGPRINT("dbgRealloc random failing\n");
            glRandomFailCount = 0;
            return NULL;
        }
    }

    if (mem != NULL)
    {
	// Verify that the signature is not corrupted.
        
        pmh = MEM_HDR_PTR(mem);
        if (pmh->signature[0] != MEM_ALLOC_SIG ||
            pmh->signature[1] != MEM_ALLOC_SIG ||
            pmh->signature[2] != MEM_ALLOC_SIG)
        {
            WARNING("Possible memory corruption\n");
        }

        AdjustSizes(-(LONG)pmh->nbytes, pmh);
        
        // Reallocate nbytes+extra bytes.
        memNew = HeapReAlloc(GetProcessHeap(), 0, pmh, nbytes+MEM_HDR_SIZE);
    }
    else
    {
        // Old memory pointer is NULL, so allocate a new chunk.
        memNew = HeapAlloc(GetProcessHeap(), 0, nbytes+MEM_HDR_SIZE);

        // We've allocated new memory so initialize its signature.
        if (memNew != NULL)
        {
            pmh = (MEM_HDR *)memNew;
            pmh->signature[0] = MEM_ALLOC_SIG;
            pmh->signature[1] = MEM_ALLOC_SIG;
            pmh->signature[2] = MEM_ALLOC_SIG;
        }
    }

    if (memNew != NULL)
    {
        // Do house keeping and update allocation size so far.

        AdjustSizes(nbytes, memNew);
        pmh = (MEM_HDR *)memNew;
        pmh->nbytes = nbytes;
        memNew = (PVOID) (pmh+1);
    }
    else
    {
        if (mem != NULL)
        {
            AdjustSizes((LONG)pmh->nbytes, pmh);
        }
        
        DBGLEVEL1(LEVEL_ERROR, "dbgRealloc could not allocate %u bytes\n",
                  nbytes);
    }

    DBGLEVEL3(LEVEL_ALLOC, "dbgRealloc of 0x%X:%u returned 0x%x\n",
              mem, nbytes, memNew);

    return memNew;
}

int FASTCALL
dbgMemSize(void *mem)
{
    MEM_HDR *pmh;
    
    pmh = MEM_HDR_PTR(mem);
    
    if (pmh->signature[0] != MEM_ALLOC_SIG ||
        pmh->signature[1] != MEM_ALLOC_SIG ||
        pmh->signature[2] != MEM_ALLOC_SIG)
    {
        return -1;
    }
    
    return (int)pmh->nbytes;
}

#endif // DBG

ULONG APIENTRY glDebugEntry(int param, void *data)
{
#if DBG
    switch(param)
    {
    case 0:
	return glSize;
    case 1:
	return glHighWater;
    case 2:
	return glReal;
    case 3:
        return dbgMemSize(data);
    }
#endif
    return 0;
}

#define MEM_ALIGN 32

void * FASTCALL
AllocAlign32(UINT nbytes)
{
    void *mem;
    void **aligned;

    // We allocate enough extra memory for the alignment and our header
    // which just consists of a pointer:

    mem = ALLOC(nbytes + MEM_ALIGN + sizeof(void *));
    if (!mem)
    {
        DBGLEVEL1(LEVEL_ERROR, "AllocAlign32 could not allocate %u bytes\n",
                  nbytes);
        return NULL;
    }

    aligned = (void **)(((ULONG_PTR)mem + sizeof(void *) +
                         (MEM_ALIGN - 1)) & ~(MEM_ALIGN - 1));
    *(aligned-1) = mem;
    
    return aligned;
}

void FASTCALL
FreeAlign32(void *mem)
{
    if ( NULL == mem )
    {
        DBGERROR("NULL pointer passed to FreeAlign32\n");
        return;
    }

    FREE(*((void **)mem-1));
}

void * FASTCALL
gcAlloc( __GLcontext *gc, UINT nbytes, DWORD flags )
{
    void *mem;

#if DBG
    mem = dbgAlloc(nbytes, flags);
#else
    mem = HeapAlloc(GetProcessHeap(), flags, nbytes);
#endif
    if (NULL == mem)
    {
        ((__GLGENcontext *)gc)->errorcode = GLGEN_OUT_OF_MEMORY;
        __glSetErrorEarly(gc, GL_OUT_OF_MEMORY);
    }
    return mem;
}

void * FASTCALL
GCREALLOC( __GLcontext *gc, void *mem, UINT nbytes )
{
    void *newMem;

    // The Win32 realloc functions do not have free-on-zero behavior,
    // so fake it.
    if (nbytes == 0)
    {
	if (mem != NULL)
	{
	    FREE(mem);
	}
	return NULL;
    }

    // The Win32 realloc functions don't handle a NULL old pointer,
    // so explicitly turn such calls into allocs.
    if (mem == NULL)
    {
	newMem = ALLOC(nbytes);
    }
    else
    {
	newMem = REALLOC(mem, nbytes);
    }

    if (NULL == newMem)
    {
        ((__GLGENcontext *)gc)->errorcode = GLGEN_OUT_OF_MEMORY;
        __glSetErrorEarly(gc, GL_OUT_OF_MEMORY);
    }

    return newMem;
}

void * FASTCALL
GCALLOCALIGN32( __GLcontext *gc, UINT nbytes )
{
    void *mem;

    mem = AllocAlign32(nbytes);
    if (NULL == mem)
    {
        ((__GLGENcontext *)gc)->errorcode = GLGEN_OUT_OF_MEMORY;
        __glSetErrorEarly(gc, GL_OUT_OF_MEMORY);
    }
    return mem;
}

// Tunable parameters for temporary memory allocation

#define MAX_TEMP_BUFFERS    4
#define TEMP_BUFFER_SIZE    4096

struct MemHeaderRec
{
    LONG  inUse;
    ULONG nbytes;
    void  *mem;
};

typedef struct MemHeaderRec MemHeader;

MemHeader TempMemHeader[MAX_TEMP_BUFFERS];

// InitTempAlloc
//      Initializes the temporary memory allocation header and allocates the
//      temporary memory buffers.
//
// Synopsis:
//      BOOL InitTempAlloc()
//
// History:
//      02-DEC-93 Eddie Robinson [v-eddier] Wrote it.
//
BOOL FASTCALL
InitTempAlloc(void)
{
    int   i;
    PBYTE buffers;
    static LONG initCount = -1;
    
    if (initCount >= 0)
        return TRUE;

    if (InterlockedIncrement(&initCount) != 0)
        return TRUE;

// Allocate buffers for the first time.

    buffers = ALLOC(MAX_TEMP_BUFFERS*TEMP_BUFFER_SIZE);
    if (!buffers)
    {
        InterlockedDecrement(&initCount);           // try again later
        return FALSE;
    }

    for (i = 0; i < MAX_TEMP_BUFFERS; i++)
    {
        TempMemHeader[i].nbytes = TEMP_BUFFER_SIZE;
        TempMemHeader[i].mem = (void *) buffers;
        TempMemHeader[i].inUse = -1;      // must be last
        buffers += TEMP_BUFFER_SIZE;
    }
    
    return TRUE;
}                                  

// gcTempAlloc
//      Allocates temporary memory from a static array, if possible.  Otherwise
//      it calls ALLOC
//
// Synopsis:
//      void * gcTempAlloc(__GLcontext *gc, UINT nbytes)
//          gc      points to the OpenGL context structure
//          nbytes  specifies the number of bytes to allocate
//
// History:
//  02-DEC-93 Eddie Robinson [v-eddier] Wrote it.
//
void * FASTCALL
gcTempAlloc(__GLcontext *gc, UINT nbytes)
{
    int i;
    void *mem;

    if (nbytes == 0)
    {
        // Zero-byte allocations do occur so don't make this a warning
        // to avoid excessive debug spew.
        DBGLEVEL(LEVEL_ALLOC, "gcTempAlloc: failing zero byte alloc\n");
        return NULL;
    }
    
    for (i = 0; i < MAX_TEMP_BUFFERS; i++)
    {
        if (nbytes <= TempMemHeader[i].nbytes)
        {
            if (InterlockedIncrement(&TempMemHeader[i].inUse))
            {
                InterlockedDecrement(&TempMemHeader[i].inUse);
            }
            else
            {
                DBGLEVEL2(LEVEL_ALLOC, "gcTempAlloc of %u returned 0x%x\n",
                          nbytes, TempMemHeader[i].mem);
                GC_TEMP_BUFFER_ALLOC(gc, TempMemHeader[i].mem);
                return(TempMemHeader[i].mem);
            }
        }
    }
    
    mem = ALLOC(nbytes);
    if (!mem)
    {
        WARNING1("gcTempAlloc: memory allocation error size %d\n", nbytes);
        ((__GLGENcontext *)gc)->errorcode = GLGEN_OUT_OF_MEMORY;
        __glSetErrorEarly(gc, GL_OUT_OF_MEMORY);
    }
    
    DBGLEVEL2(LEVEL_ALLOC, "gcTempAlloc of %u returned 0x%x\n", nbytes, mem);
    GC_TEMP_BUFFER_ALLOC(gc, mem);
    
    return mem;
}

// gcTempFree
//      Marks allocated static buffer as unused or calls FREE.
//
// Synopsis:
//      void gcTempFree(__GLcontext *gc, void *mem)
//          mem    specifies the adress of the memory to free
//
// History:
//  02-DEC-93 Eddie Robinson [v-eddier] Wrote it.
//
void FASTCALL
gcTempFree(__GLcontext *gc, void *mem)
{
    int i;
    
    DBGLEVEL1(LEVEL_ALLOC, "gcTempFree of 0x%x\n", mem);

    GC_TEMP_BUFFER_FREE(gc, mem);
    for (i = 0; i < MAX_TEMP_BUFFERS; i++)
    {
        if (mem == TempMemHeader[i].mem)
        {
            InterlockedDecrement(&TempMemHeader[i].inUse);
            return;
        }
    }
    
    FREE( mem );
}