mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-28 23:12:04 +00:00
1825 lines
39 KiB
C++
1825 lines
39 KiB
C++
|
|
/*
|
|
* UNPUBLISHED -- Rights reserved under the copyright laws of the
|
|
* United States. Use of a copyright notice is precautionary only and
|
|
* does not imply publication or disclosure.
|
|
*
|
|
* THIS DOCUMENTATION CONTAINS CONFIDENTIAL AND PROPRIETARY INFORMATION
|
|
* OF VICARIOUS VISIONS, INC. ANY DUPLICATION, MODIFICATION,
|
|
* DISTRIBUTION, OR DISCLOSURE IS STRICTLY PROHIBITED WITHOUT THE PRIOR
|
|
* EXPRESS WRITTEN PERMISSION OF VICARIOUS VISIONS, INC.
|
|
*/
|
|
|
|
/*
|
|
* ZONE MEMORY MANAGER
|
|
*
|
|
* Goals:
|
|
* 1. Minimize overhead
|
|
* 2. Minimize fragmentation
|
|
*
|
|
* Constraints:
|
|
* 1. Maximum allocated block size is 32MB
|
|
* 2. Maximum 64 different memory tags supported
|
|
* 3. Maximum 256 byte alignment
|
|
*
|
|
* All memory required by the manager is allocated at startup in
|
|
* the form of one large pool.
|
|
*
|
|
* Allocated blocks require a 4 byte header to store size, tag, and
|
|
* alignment information. Blocks that need to support the Z_TagFree()
|
|
* feature require an additional 8 byte link list structure.
|
|
*
|
|
* Free blocks require a 16 bytes of tracking information. If possible
|
|
* this information is stored directly in the block (which is in the
|
|
* pool.) If the free block is not large enough, its information is
|
|
* stored in an overflow buffer.
|
|
*
|
|
* In an effort to reduce fragmentation, blocks allocated for a short
|
|
* period of time at the end of the pool. All other blocks are allocated
|
|
* at the start. Allocation is first fit.
|
|
*
|
|
*/
|
|
|
|
#include "../game/q_shared.h"
|
|
#include "qcommon.h"
|
|
#include "../renderer/qgl_console.h"
|
|
|
|
#ifdef _GAMECUBE
|
|
#include <dolphin/os.h>
|
|
#endif
|
|
|
|
#ifdef _WINDOWS
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#ifdef _XBOX
|
|
#include <Xtl.h>
|
|
#endif
|
|
|
|
// Where do hunk allocations go?
|
|
static memtag_t hunk_tag;
|
|
|
|
// Used to mark the start and end of blocks in debug mode
|
|
#define ZONE_MAGIC 0xfe
|
|
|
|
// Size of the free block overflow buffer
|
|
#define ZONE_FREE_OVERFLOW 4096
|
|
|
|
// Indicates whether or not special (slow) debug code should be enabled
|
|
#define ZONE_DEBUG 0
|
|
|
|
// Allocate all available memory minus this amount
|
|
#ifdef _GAMECUBE
|
|
#define ZONE_HEAP_FREE_DEBUG (64*1024*4)
|
|
#define ZONE_HEAP_FREE_RELEASE (0)
|
|
|
|
#ifdef _DEBUG
|
|
# define ZONE_HEAP_FREE ZONE_HEAP_FREE_DEBUG
|
|
#else
|
|
# define ZONE_HEAP_FREE ZONE_HEAP_FREE_RELEASE
|
|
#endif
|
|
#else
|
|
//Game needs about 8 MB for framebuffers audio, bink, etc., plus 24 MB
|
|
//for textures.
|
|
//# define ZONE_HEAP_FREE (1024*1024*8 + 24*1024*1024)
|
|
# define ZONE_HEAP_FREE (1024*1024*8 + 24*1024*1024 + 4*1024*1024)
|
|
#endif
|
|
|
|
// Should we emulate the smaller memory footprint of actual release systems?
|
|
#define ZONE_EMULATE_SPACE 0
|
|
|
|
// All standard header data is crammed into 4 bytes
|
|
typedef unsigned int ZoneHeader;
|
|
|
|
// Debug markers to check for overflow/underflow
|
|
typedef unsigned int ZoneDebugHeader;
|
|
typedef unsigned char ZoneDebugFooter;
|
|
|
|
// Extended header information for memory freed with TagFree()
|
|
struct ZoneLinkHeader
|
|
{
|
|
ZoneLinkHeader* m_Next;
|
|
ZoneLinkHeader* m_Prev;
|
|
};
|
|
|
|
static ZoneLinkHeader* s_LinkBase;
|
|
|
|
// Free memory block tracking information
|
|
struct ZoneFreeBlock
|
|
{
|
|
unsigned int m_Address;
|
|
unsigned int m_Size;
|
|
ZoneFreeBlock* m_Next;
|
|
ZoneFreeBlock* m_Prev;
|
|
};
|
|
|
|
// Buffer to hold free memory information that we can't
|
|
// fit directly in the pool
|
|
static ZoneFreeBlock s_FreeOverflow[ZONE_FREE_OVERFLOW];
|
|
static int s_LastOverflowIndex;
|
|
|
|
static ZoneFreeBlock s_FreeStart;
|
|
static ZoneFreeBlock s_FreeEnd;
|
|
|
|
// Various stats collected at runtime
|
|
struct ZoneStats
|
|
{
|
|
int m_CountAlloc;
|
|
int m_SizeAlloc;
|
|
int m_OverheadAlloc;
|
|
int m_PeakAlloc;
|
|
int m_CountFree;
|
|
int m_SizeFree;
|
|
int m_SizesPerTag[TAG_COUNT];
|
|
int m_CountsPerTag[TAG_COUNT];
|
|
};
|
|
|
|
static ZoneStats s_Stats;
|
|
|
|
// Special empty block for zero size allocations
|
|
struct ZoneEmptyBlock
|
|
{
|
|
ZoneHeader header;
|
|
#ifdef _DEBUG
|
|
ZoneDebugHeader start;
|
|
ZoneDebugFooter end;
|
|
#endif
|
|
};
|
|
|
|
#ifdef _DEBUG
|
|
static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25, ZONE_MAGIC, ZONE_MAGIC};
|
|
#else
|
|
static ZoneEmptyBlock s_EmptyBlock = {TAG_STATIC << 25};
|
|
#endif
|
|
|
|
// Free block jump table for fast memory deallocation
|
|
#define Z_JUMP_TABLE_SIZE 64
|
|
static ZoneFreeBlock* s_FreeJumpTable[Z_JUMP_TABLE_SIZE];
|
|
static unsigned int s_FreeJumpResolution;
|
|
|
|
static void* s_PoolBase;
|
|
static bool s_Initialized = false;
|
|
static bool s_IsNewDeleteTemp = false;
|
|
|
|
#ifndef _GAMECUBE
|
|
static HANDLE s_Mutex = INVALID_HANDLE_VALUE;
|
|
#endif
|
|
|
|
static void Z_Stats_f(void);
|
|
void Z_Details_f(void);
|
|
void Z_DumpMemMap_f(void);
|
|
|
|
|
|
#ifdef _XBOX
|
|
void ShowOSMemory(void)
|
|
{
|
|
MEMORYSTATUS stat;
|
|
GlobalMemoryStatus(&stat);
|
|
Com_Printf(" total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024,
|
|
stat.dwAvailPhys / 1024);
|
|
FILE *out = fopen("d:\\osmem.txt", "a");
|
|
if(out) {
|
|
fprintf(out, "total mem: %d, free mem: %d\n", stat.dwTotalPhys / 1024,
|
|
stat.dwAvailPhys / 1024);
|
|
fclose(out);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
int Z_MemFree(void)
|
|
{
|
|
return s_Stats.m_SizeFree;
|
|
}
|
|
|
|
|
|
void Com_InitZoneMemory(void)
|
|
{
|
|
// assert(!s_Initialized);
|
|
// Zone now initializes on first use, can't reliably assume anything here
|
|
if (s_Initialized)
|
|
return;
|
|
|
|
Com_Printf("Initialising zone memory .....\n");
|
|
|
|
// Clear some globals
|
|
memset(&s_Stats, 0, sizeof(s_Stats));
|
|
memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow));
|
|
s_LastOverflowIndex = 0;
|
|
s_LinkBase = NULL;
|
|
s_IsNewDeleteTemp = false;
|
|
|
|
// Alloc the pool
|
|
#if defined(_XBOX)
|
|
MEMORYSTATUS status;
|
|
GlobalMemoryStatus(&status);
|
|
|
|
// BTO : VVFIXME - Extra little note to see how much memory
|
|
// is being used by globals/statics
|
|
Com_Printf("*** PhysRAM: %d used, %d free\n",
|
|
status.dwTotalPhys-status.dwAvailPhys,
|
|
status.dwAvailPhys);
|
|
SIZE_T size;
|
|
# if ZONE_EMULATE_SPACE
|
|
#ifdef _DEBUG
|
|
//Emulated space is always about 6 megs off from release build. Try
|
|
//to compensate. This number may need tweaking in the future.
|
|
SIZE_T exe = 6500 * 1024;
|
|
#else
|
|
SIZE_T exe = 0; //Exe size is already reflected in GlobalMemoryStatus().
|
|
#endif
|
|
size = 0x4000000 - (exe + ZONE_HEAP_FREE);
|
|
# else
|
|
size = status.dwAvailPhys - ZONE_HEAP_FREE;
|
|
# endif
|
|
s_PoolBase = GlobalAlloc(0, size);
|
|
#elif defined(_WINDOWS)
|
|
SIZE_T size = 50*1024*1024;
|
|
s_PoolBase = GlobalAlloc(0, size);
|
|
#endif
|
|
|
|
// Setup the initial free block
|
|
ZoneFreeBlock* base = (ZoneFreeBlock*)s_PoolBase;
|
|
base->m_Address = (unsigned int)s_PoolBase;
|
|
base->m_Size = size;
|
|
base->m_Next = &s_FreeEnd;
|
|
base->m_Prev = &s_FreeStart;
|
|
|
|
// Init the free block jump table
|
|
memset(s_FreeJumpTable, 0, Z_JUMP_TABLE_SIZE * sizeof(ZoneFreeBlock*));
|
|
s_FreeJumpResolution = (size / Z_JUMP_TABLE_SIZE) + 1;
|
|
s_FreeJumpTable[0] = base;
|
|
|
|
// Setup free block dummies
|
|
s_FreeStart.m_Address = 0;
|
|
s_FreeStart.m_Size = 0;
|
|
s_FreeStart.m_Next = base;
|
|
s_FreeStart.m_Prev = NULL;
|
|
|
|
s_FreeEnd.m_Address = 0xFFFFFFFF;
|
|
s_FreeEnd.m_Size = 0;
|
|
s_FreeEnd.m_Next = NULL;
|
|
s_FreeEnd.m_Prev = base;
|
|
|
|
s_Stats.m_CountFree = 1;
|
|
s_Stats.m_SizeFree = size;
|
|
|
|
s_Initialized = true;
|
|
|
|
// Add some commands
|
|
Cmd_AddCommand("zone_stats", Z_Stats_f);
|
|
Cmd_AddCommand("zone_details", Z_Details_f);
|
|
Cmd_AddCommand("zone_memmap", Z_DumpMemMap_f);
|
|
|
|
#ifndef _GAMECUBE
|
|
s_Mutex = CreateMutex(NULL, FALSE, NULL);
|
|
#endif
|
|
}
|
|
|
|
void Com_ShutdownZoneMemory(void)
|
|
{
|
|
assert(s_Initialized);
|
|
|
|
// Remove commands
|
|
Cmd_RemoveCommand("zone_stats");
|
|
Cmd_RemoveCommand("zone_details");
|
|
Cmd_RemoveCommand("zone_memmap");
|
|
|
|
if (s_Stats.m_CountAlloc)
|
|
{
|
|
// Free all memory
|
|
// CM_ReleaseVisData();
|
|
Z_TagFree(TAG_ALL);
|
|
}
|
|
|
|
// Clear some globals
|
|
memset(&s_Stats, 0, sizeof(s_Stats));
|
|
memset(s_FreeOverflow, 0, sizeof(s_FreeOverflow));
|
|
s_LastOverflowIndex = 0;
|
|
s_LinkBase = NULL;
|
|
|
|
// Free the pool
|
|
#ifndef _GAMECUBE
|
|
GlobalFree(s_PoolBase);
|
|
CloseHandle(s_Mutex);
|
|
#endif
|
|
|
|
s_PoolBase = NULL;
|
|
s_Initialized = false;
|
|
}
|
|
|
|
|
|
// Determine if a tag should only be allocated for a very
|
|
// short period of time.
|
|
static bool Z_IsTagTemp(memtag_t eTag)
|
|
{
|
|
return
|
|
eTag == TAG_TEMP_WORKSPACE ||
|
|
#ifndef _JK2MP
|
|
eTag == TAG_TEMP_SAVEGAME_WORKSPACE ||
|
|
eTag == TAG_STRING ||
|
|
eTag == TAG_GP2 ||
|
|
#endif
|
|
eTag == TAG_SND_RAWDATA ||
|
|
eTag == TAG_ICARUS ||
|
|
#ifdef _JK2MP
|
|
eTag == TAG_TEXTPOOL ||
|
|
eTag == TAG_TEMP_HUNKALLOC ||
|
|
#endif
|
|
eTag == TAG_LISTFILES;
|
|
}
|
|
|
|
// Determine if a tag needs TagFree() support.
|
|
static bool Z_IsTagLinked(memtag_t eTag)
|
|
{
|
|
return
|
|
eTag == TAG_BSP ||
|
|
#ifndef _JK2MP
|
|
eTag == TAG_HUNKALLOC ||
|
|
eTag == TAG_HUNKMISCMODELS ||
|
|
eTag == TAG_G_ALLOC ||
|
|
#endif
|
|
#ifdef _JK2MP
|
|
eTag == TAG_CG_UI_ALLOC ||
|
|
eTag == TAG_BG_ALLOC ||
|
|
eTag == TAG_HUNK_MARK1 ||
|
|
eTag == TAG_HUNK_MARK2 ||
|
|
eTag == TAG_TEMP_HUNKALLOC ||
|
|
#endif
|
|
eTag == TAG_UI_ALLOC;
|
|
}
|
|
|
|
static int Z_CalcAlignmentPad(int iAlign, unsigned int iAddress, unsigned int iOffset,
|
|
unsigned int iSize, unsigned int iHeaderSize, unsigned int iFooterSize)
|
|
{
|
|
int align_size;
|
|
|
|
if (iAlign == 0) return 0;
|
|
|
|
if (iOffset == 0)
|
|
{
|
|
// Align data at low end of block
|
|
align_size = iAlign -
|
|
((iAddress + iHeaderSize) % iAlign);
|
|
}
|
|
else
|
|
{
|
|
// Align data at high end of block
|
|
unsigned int block_start = iAddress + iOffset -
|
|
iSize + iHeaderSize;
|
|
align_size = block_start % iAlign;
|
|
}
|
|
|
|
if (align_size == iAlign)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return align_size;
|
|
}
|
|
|
|
static ZoneFreeBlock* Z_GetOverflowBlock(void)
|
|
{
|
|
for (int i = s_LastOverflowIndex; i < ZONE_FREE_OVERFLOW; ++i)
|
|
{
|
|
if (s_FreeOverflow[i].m_Address == 0)
|
|
{
|
|
s_LastOverflowIndex = i;
|
|
return &s_FreeOverflow[i];
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < s_LastOverflowIndex; ++j)
|
|
{
|
|
if (s_FreeOverflow[j].m_Address == 0)
|
|
{
|
|
s_LastOverflowIndex = j;
|
|
return &s_FreeOverflow[j];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static inline bool Z_IsFreeBlockLargeEnough(ZoneFreeBlock* pBlock, int iSize,
|
|
int iHeaderSize, int iFooterSize, int iAlign, bool bLow, int& iAlignPad)
|
|
{
|
|
// Is the block large enough?
|
|
if (pBlock->m_Size >= iSize)
|
|
{
|
|
if (iAlign > 0)
|
|
{
|
|
// If we need some aligment, we need to check size
|
|
// against that as well.
|
|
iAlignPad = Z_CalcAlignmentPad(iAlign,
|
|
pBlock->m_Address, !bLow ? pBlock->m_Size : 0,
|
|
iSize, iHeaderSize, iFooterSize);
|
|
|
|
if (pBlock->m_Size < iAlignPad + iSize)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static ZoneFreeBlock* Z_FindFirstFree(int iSize, int iHeaderSize,
|
|
int iFooterSize, int iAlign, int& iAlignPad)
|
|
{
|
|
for (ZoneFreeBlock* block = s_FreeStart.m_Next; block; block = block->m_Next)
|
|
{
|
|
if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize,
|
|
iAlign, true, iAlignPad))
|
|
{
|
|
return block;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static ZoneFreeBlock* Z_FindLastFree(int iSize, int iHeaderSize,
|
|
int iFooterSize, int iAlign, int& iAlignPad)
|
|
{
|
|
for (ZoneFreeBlock* block = s_FreeEnd.m_Prev; block; block = block->m_Prev)
|
|
{
|
|
if (Z_IsFreeBlockLargeEnough(block, iSize, iHeaderSize, iFooterSize,
|
|
iAlign, false, iAlignPad))
|
|
{
|
|
return block;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bool Z_ValidateFree(void)
|
|
{
|
|
#if ZONE_DEBUG
|
|
// Make sure no free blocks are overlapping
|
|
for (ZoneFreeBlock* a = &s_FreeStart; a; a = a->m_Next)
|
|
{
|
|
if (a->m_Address == 0 && a->m_Size != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (ZoneFreeBlock* b = &s_FreeStart; b; b = b->m_Next)
|
|
{
|
|
if (a != b &&
|
|
a->m_Address >= b->m_Address &&
|
|
a->m_Address < b->m_Address + b->m_Size)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool Z_ValidateLinks(void)
|
|
{
|
|
#if ZONE_DEBUG
|
|
// Make sure links are sane
|
|
for (ZoneLinkHeader* a = s_LinkBase; a; a = a->m_Next)
|
|
{
|
|
if ((a->m_Next && a != a->m_Next->m_Prev) ||
|
|
(a->m_Prev && a != a->m_Prev->m_Next))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
static int Z_GetJumpTableIndex(unsigned int iAddress)
|
|
{
|
|
int index = (iAddress - (unsigned int)s_PoolBase) / s_FreeJumpResolution;
|
|
if (index < 0) return 0;
|
|
if (index >= Z_JUMP_TABLE_SIZE) return Z_JUMP_TABLE_SIZE - 1;
|
|
return index;
|
|
}
|
|
|
|
static ZoneFreeBlock* Z_GetFreeBlockBefore(unsigned int iAddress)
|
|
{
|
|
// Find this block's position in the jump table
|
|
int index = Z_GetJumpTableIndex(iAddress) - 1;
|
|
|
|
// Find a valid jump table entry
|
|
while (index >= 0 && !s_FreeJumpTable[index]) --index;
|
|
|
|
if (index < 0) return &s_FreeStart;
|
|
return s_FreeJumpTable[index];
|
|
}
|
|
|
|
static void Z_RemoveFromJumpTable(ZoneFreeBlock* pBlock)
|
|
{
|
|
// Is this block in the jump table?
|
|
int index = Z_GetJumpTableIndex(pBlock->m_Address);
|
|
if (s_FreeJumpTable[index] == pBlock)
|
|
{
|
|
// See if the next block will fit in our slot
|
|
if (pBlock->m_Next != &s_FreeEnd)
|
|
{
|
|
int nindex = Z_GetJumpTableIndex(pBlock->m_Next->m_Address);
|
|
if (nindex == index)
|
|
{
|
|
s_FreeJumpTable[index] = pBlock->m_Next;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// See if the previous block will fit in our slot
|
|
if (pBlock->m_Prev != &s_FreeStart)
|
|
{
|
|
int pindex = Z_GetJumpTableIndex(pBlock->m_Prev->m_Address);
|
|
if (pindex == index)
|
|
{
|
|
s_FreeJumpTable[index] = pBlock->m_Prev;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// No other free blocks fit here, give up
|
|
s_FreeJumpTable[index] = NULL;
|
|
}
|
|
}
|
|
|
|
static void Z_LinkFreeBlock(ZoneFreeBlock* pBlock)
|
|
{
|
|
ZoneFreeBlock* cur = Z_GetFreeBlockBefore(pBlock->m_Address);
|
|
for (; cur; cur = cur->m_Next)
|
|
{
|
|
// Find the correct position, ordered by address
|
|
if (cur->m_Address > pBlock->m_Address)
|
|
{
|
|
// Link up the block
|
|
pBlock->m_Next = cur;
|
|
pBlock->m_Prev = cur->m_Prev;
|
|
cur->m_Prev->m_Next = pBlock;
|
|
cur->m_Prev = pBlock;
|
|
|
|
// Update the jump table if necessary
|
|
int index = Z_GetJumpTableIndex(pBlock->m_Address);
|
|
if (!s_FreeJumpTable[index])
|
|
{
|
|
s_FreeJumpTable[index] = pBlock;
|
|
}
|
|
|
|
s_Stats.m_CountFree++;
|
|
s_Stats.m_SizeFree += pBlock->m_Size;
|
|
|
|
assert(Z_ValidateFree());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void* Z_SplitFree(ZoneFreeBlock* pBlock, int iSize, bool bLow)
|
|
{
|
|
assert(pBlock->m_Size >= iSize);
|
|
|
|
Z_RemoveFromJumpTable(pBlock);
|
|
|
|
// Delink the free block
|
|
ZoneFreeBlock fblock = *pBlock;
|
|
pBlock->m_Prev->m_Next = pBlock->m_Next;
|
|
pBlock->m_Next->m_Prev = pBlock->m_Prev;
|
|
pBlock->m_Address = 0;
|
|
|
|
s_Stats.m_CountFree--;
|
|
s_Stats.m_SizeFree -= pBlock->m_Size;
|
|
assert(Z_ValidateFree());
|
|
|
|
if (fblock.m_Size > iSize)
|
|
{
|
|
// Split the block into an allocated and free portion
|
|
int remainder = fblock.m_Size - iSize;
|
|
|
|
if (remainder < sizeof(ZoneFreeBlock))
|
|
{
|
|
// Free portion is not large to hold free info --
|
|
// we're going to have to use the overflow buffer.
|
|
ZoneFreeBlock* nblock = Z_GetOverflowBlock();
|
|
|
|
if (nblock == NULL)
|
|
{
|
|
Z_Details_f();
|
|
Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!");
|
|
}
|
|
|
|
// Split the block
|
|
void* ret;
|
|
if (bLow)
|
|
{
|
|
ret = (void*)fblock.m_Address;
|
|
nblock->m_Address = fblock.m_Address + iSize;
|
|
}
|
|
else
|
|
{
|
|
ret = (void*)(fblock.m_Address + remainder);
|
|
nblock->m_Address = fblock.m_Address;
|
|
}
|
|
|
|
nblock->m_Size = remainder;
|
|
Z_LinkFreeBlock(nblock);
|
|
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
// Free portion is large enough -- split it
|
|
void* ret;
|
|
ZoneFreeBlock* nblock;
|
|
if (bLow)
|
|
{
|
|
ret = (void*)fblock.m_Address;
|
|
nblock = (ZoneFreeBlock*)(fblock.m_Address + iSize);
|
|
}
|
|
else
|
|
{
|
|
ret = (void*)(fblock.m_Address + remainder);
|
|
nblock = (ZoneFreeBlock*)fblock.m_Address;
|
|
}
|
|
|
|
nblock->m_Address = (unsigned int)nblock;
|
|
nblock->m_Size = remainder;
|
|
|
|
Z_LinkFreeBlock(nblock);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No need to split, just return block.
|
|
return (void*)fblock.m_Address;
|
|
}
|
|
}
|
|
|
|
static void Z_SetupAlignmentPad(void* pBlock, int iAlignPad, bool bLow)
|
|
{
|
|
// Clear alignment bytes
|
|
memset(pBlock, 0, iAlignPad);
|
|
|
|
// If we have more than 1 alignment byte, the first align byte
|
|
// tells us how many additional bytes we have.
|
|
if (iAlignPad > 1)
|
|
{
|
|
assert(iAlignPad < 256);
|
|
unsigned char* ptr;
|
|
if (bLow)
|
|
{
|
|
ptr = (unsigned char*)pBlock + (iAlignPad - 1);
|
|
}
|
|
else
|
|
{
|
|
ptr = (unsigned char*)pBlock;
|
|
}
|
|
*ptr = iAlignPad - 1;
|
|
}
|
|
}
|
|
|
|
void Z_MallocFail(const char* pMessage, int iSize, memtag_t eTag)
|
|
{
|
|
// Report the error
|
|
// Com_Printf("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag);
|
|
Com_Printf("Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag);
|
|
Z_Details_f();
|
|
Z_DumpMemMap_f();
|
|
// Com_Printf("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag);
|
|
Com_Printf("(Repeat): Z_Malloc(): %s : %d bytes and tag %d !!!!\n", pMessage, iSize, eTag);
|
|
|
|
// Clear the screen blue to indicate out of memory
|
|
for (;;)
|
|
{
|
|
qglBeginFrame();
|
|
qglClearColor(0, 0, 1, 1);
|
|
qglClear(GL_COLOR_BUFFER_BIT);
|
|
qglEndFrame();
|
|
}
|
|
}
|
|
|
|
void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign)
|
|
{
|
|
// assert(s_Initialized);
|
|
// Zone now initializes on first use. (During static constructors)
|
|
if (!s_Initialized)
|
|
Com_InitZoneMemory();
|
|
|
|
if (iSize == 0)
|
|
{
|
|
#ifdef _DEBUG
|
|
return (void*)(&s_EmptyBlock.start + 1);
|
|
#else
|
|
return (void*)(&s_EmptyBlock.header + 1);
|
|
#endif
|
|
}
|
|
|
|
if (iSize < 0)
|
|
{
|
|
Z_MallocFail("Negative size", iSize, eTag);
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef _GAMECUBE
|
|
WaitForSingleObject(s_Mutex, INFINITE);
|
|
#endif
|
|
|
|
// Make new/delete memory temporary if requested
|
|
if (eTag == TAG_NEWDEL && s_IsNewDeleteTemp)
|
|
{
|
|
eTag = TAG_TEMP_WORKSPACE;
|
|
}
|
|
|
|
// Determine how much space we need with headers and footers
|
|
int header_size = sizeof(ZoneHeader);
|
|
int footer_size = 0;
|
|
if (Z_IsTagLinked(eTag))
|
|
{
|
|
header_size += sizeof(ZoneLinkHeader);
|
|
}
|
|
#ifdef _DEBUG
|
|
header_size += sizeof(ZoneDebugHeader);
|
|
footer_size += sizeof(ZoneDebugFooter);
|
|
#endif
|
|
int real_size = iSize + header_size + footer_size;
|
|
int align_pad = 0;
|
|
|
|
// Get a bit of free memory. Temporary memory is allocated
|
|
// from the end. More permanent allocations are done at the
|
|
// begining of the pool.
|
|
ZoneFreeBlock* fblock;
|
|
if (Z_IsTagTemp(eTag))
|
|
{
|
|
fblock = Z_FindLastFree(real_size, header_size, footer_size,
|
|
iAlign, align_pad);
|
|
}
|
|
else
|
|
{
|
|
fblock = Z_FindFirstFree(real_size, header_size, footer_size,
|
|
iAlign, align_pad);
|
|
}
|
|
|
|
// Did we actually find some memory?
|
|
if (!fblock)
|
|
{
|
|
#ifndef _GAMECUBE
|
|
ReleaseMutex(s_Mutex);
|
|
#endif
|
|
// if(eTag == TAG_TEMP_SND_RAWDATA) {
|
|
if(eTag == TAG_SND_RAWDATA) {
|
|
return NULL;
|
|
}
|
|
|
|
Z_MallocFail("Out of memory", iSize, eTag);
|
|
return NULL;
|
|
}
|
|
|
|
// Add any alignment bytes
|
|
real_size += align_pad;
|
|
|
|
// Split the free block and get a pointer to the start
|
|
// allocated space.
|
|
void* ablock;
|
|
if (Z_IsTagTemp(eTag))
|
|
{
|
|
ablock = Z_SplitFree(fblock, real_size, false);
|
|
|
|
// Append align pad to end of block
|
|
Z_SetupAlignmentPad(
|
|
(void*)((char*)ablock + real_size - align_pad),
|
|
align_pad, false);
|
|
}
|
|
else
|
|
{
|
|
ablock = Z_SplitFree(fblock, real_size, true);
|
|
|
|
// Insert align pad at block start
|
|
Z_SetupAlignmentPad(ablock, align_pad, true);
|
|
ablock = (void*)((char*)ablock + align_pad);
|
|
}
|
|
|
|
if (!ablock)
|
|
{
|
|
Z_MallocFail("Failed to split", iSize, eTag);
|
|
}
|
|
|
|
// Add linking header if necessary
|
|
if (Z_IsTagLinked(eTag))
|
|
{
|
|
ZoneLinkHeader* linked = (ZoneLinkHeader*)ablock;
|
|
linked->m_Next = s_LinkBase;
|
|
linked->m_Prev = NULL;
|
|
if (s_LinkBase)
|
|
{
|
|
s_LinkBase->m_Prev = linked;
|
|
}
|
|
s_LinkBase = linked;
|
|
|
|
assert(Z_ValidateLinks());
|
|
|
|
// Next...
|
|
ablock = (void*)((char*)ablock + sizeof(ZoneLinkHeader));
|
|
}
|
|
|
|
// Setup the header:
|
|
// 31 - alignment flag
|
|
// 25-30 - tag
|
|
// 0-24 - size without headers/footers
|
|
assert(iSize >= 0 && iSize < (1 << 25));
|
|
assert(eTag >= 0 && eTag < 64);
|
|
ZoneHeader* header = (ZoneHeader*)ablock;
|
|
*header =
|
|
(((unsigned int)eTag) << 25) |
|
|
((unsigned int)iSize);
|
|
|
|
if (align_pad)
|
|
{
|
|
*header |= (1 << 31);
|
|
}
|
|
|
|
// Next...
|
|
ablock = (void*)((char*)ablock + sizeof(ZoneHeader));
|
|
|
|
#ifdef _DEBUG
|
|
{
|
|
// Setup the debug markers
|
|
ZoneDebugHeader* debug_header = (ZoneDebugHeader*)ablock;
|
|
|
|
ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)debug_header +
|
|
(sizeof(ZoneDebugHeader) + iSize));
|
|
|
|
*debug_header = ZONE_MAGIC;
|
|
*debug_footer = ZONE_MAGIC;
|
|
|
|
// Next...
|
|
ablock = (void*)((char*)ablock + sizeof(ZoneDebugHeader));
|
|
}
|
|
#endif
|
|
|
|
// Update the stats
|
|
s_Stats.m_SizeAlloc += iSize;
|
|
s_Stats.m_OverheadAlloc += header_size + footer_size + align_pad;
|
|
s_Stats.m_SizesPerTag[eTag] += iSize;
|
|
s_Stats.m_CountAlloc++;
|
|
s_Stats.m_CountsPerTag[eTag]++;
|
|
|
|
if (s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc > s_Stats.m_PeakAlloc)
|
|
{
|
|
s_Stats.m_PeakAlloc = s_Stats.m_SizeAlloc + s_Stats.m_OverheadAlloc;
|
|
}
|
|
|
|
// Return a pointer to data memory
|
|
if (bZeroit)
|
|
{
|
|
memset(ablock, 0, iSize);
|
|
}
|
|
|
|
assert(iAlign == 0 || (unsigned int)ablock % iAlign == 0);
|
|
|
|
/*
|
|
This is useful for figuring out who's allocating a certain block of
|
|
memory. Please don't remove it.
|
|
if(eTag == TAG_NEWDEL && (unsigned int)ablock >= 0x806c0000 &&
|
|
(unsigned int)ablock <= 0x806c1000 && iSize == 24) {
|
|
int suck = 0;
|
|
}
|
|
if(eTag == TAG_SMALL && (iSize == 7 || iSize == 96)) {
|
|
int suck = 0;
|
|
}
|
|
if(eTag == TAG_CLIENTS) {
|
|
int suck = 0;
|
|
}
|
|
*/
|
|
|
|
#ifndef _GAMECUBE
|
|
ReleaseMutex(s_Mutex);
|
|
#endif
|
|
|
|
return ablock;
|
|
}
|
|
|
|
static memtag_t Z_GetTag(const ZoneHeader* header)
|
|
{
|
|
return (*header & 0x7E000000) >> 25;
|
|
}
|
|
|
|
static unsigned int Z_GetSize(const ZoneHeader* header)
|
|
{
|
|
return *header & 0x1FFFFFF;
|
|
}
|
|
|
|
static int Z_GetAlign(const ZoneHeader* header)
|
|
{
|
|
if (*header & (1 << 31))
|
|
{
|
|
unsigned char* ptr = (unsigned char*)header;
|
|
memtag_t tag = Z_GetTag(header);
|
|
|
|
// point to the first alignment block
|
|
if (Z_IsTagTemp(tag))
|
|
{
|
|
ptr += sizeof(ZoneHeader) + Z_GetSize(header);
|
|
#ifdef _DEBUG
|
|
ptr += sizeof(ZoneDebugHeader) + sizeof(ZoneDebugFooter);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if (Z_IsTagLinked(tag))
|
|
{
|
|
// skip the link header
|
|
ptr -= sizeof(ZoneLinkHeader);
|
|
}
|
|
ptr -= 1;
|
|
}
|
|
|
|
return *ptr + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int Z_Size(void *pvAddress)
|
|
{
|
|
assert(s_Initialized);
|
|
|
|
#ifdef _DEBUG
|
|
ZoneDebugHeader* debug = (ZoneDebugHeader*)pvAddress - 1;
|
|
|
|
if (*debug != ZONE_MAGIC)
|
|
{
|
|
Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!");
|
|
return 0; // won't get here
|
|
}
|
|
|
|
pvAddress = (void*)debug;
|
|
#endif
|
|
|
|
ZoneHeader* header = (ZoneHeader*)pvAddress - 1;
|
|
|
|
if (Z_GetTag(header) == TAG_STATIC)
|
|
{
|
|
return 0; // kind of
|
|
}
|
|
|
|
return Z_GetSize(header);
|
|
}
|
|
|
|
static void Z_Coalasce(ZoneFreeBlock* pBlock)
|
|
{
|
|
unsigned int size = 0;
|
|
|
|
// Find later free blocks adjacent to us
|
|
ZoneFreeBlock* end;
|
|
for (end = pBlock->m_Next;
|
|
end->m_Next;
|
|
end = end->m_Next)
|
|
{
|
|
if (end->m_Address !=
|
|
end->m_Prev->m_Address + end->m_Prev->m_Size)
|
|
{
|
|
break;
|
|
}
|
|
|
|
size += end->m_Size;
|
|
|
|
Z_RemoveFromJumpTable(end);
|
|
|
|
end->m_Address = 0; // invalidate block
|
|
s_Stats.m_CountFree--;
|
|
}
|
|
|
|
// Find previous free blocks adjacent to us
|
|
ZoneFreeBlock* start;
|
|
for (start = pBlock;
|
|
start->m_Prev;
|
|
start = start->m_Prev)
|
|
{
|
|
if (start->m_Prev->m_Address + start->m_Prev->m_Size !=
|
|
start->m_Address)
|
|
{
|
|
break;
|
|
}
|
|
|
|
size += start->m_Size;
|
|
|
|
Z_RemoveFromJumpTable(start);
|
|
|
|
start->m_Address = 0; // invalidate block
|
|
s_Stats.m_CountFree--;
|
|
}
|
|
|
|
// Do we need to coalesce some blocks?
|
|
if (start->m_Next != end)
|
|
{
|
|
start->m_Next = end;
|
|
end->m_Prev = start;
|
|
start->m_Size += size;
|
|
}
|
|
}
|
|
|
|
// Return type of Z_Free differs in SP/MP. Macro hack to wrap it up
|
|
#ifdef _JK2MP
|
|
void Z_Free(void *pvAddress)
|
|
#define Z_FREE_RETURN(x) return
|
|
#else
|
|
int Z_Free(void *pvAddress)
|
|
#define Z_FREE_RETURN(x) return (x)
|
|
#endif
|
|
{
|
|
#ifdef _WINDOWS
|
|
if (!s_Initialized) return;
|
|
#endif
|
|
|
|
assert(s_Initialized);
|
|
|
|
#ifdef _DEBUG
|
|
// check the header magic
|
|
ZoneDebugHeader* debug_header = (ZoneDebugHeader*)pvAddress - 1;
|
|
|
|
if (*debug_header != ZONE_MAGIC)
|
|
{
|
|
Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!");
|
|
Z_FREE_RETURN( 0 );
|
|
}
|
|
|
|
ZoneHeader* header = (ZoneHeader*)debug_header - 1;
|
|
|
|
// check the footer magic
|
|
ZoneDebugFooter* debug_footer = (ZoneDebugFooter*)((char*)pvAddress +
|
|
Z_GetSize(header));
|
|
|
|
if (*debug_footer != ZONE_MAGIC)
|
|
{
|
|
Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone footer!");
|
|
Z_FREE_RETURN( 0 );
|
|
}
|
|
#else
|
|
ZoneHeader* header = (ZoneHeader*)pvAddress - 1;
|
|
#endif
|
|
|
|
memtag_t tag = Z_GetTag(header);
|
|
|
|
if (tag != TAG_STATIC)
|
|
{
|
|
#ifndef _GAMECUBE
|
|
WaitForSingleObject(s_Mutex, INFINITE);
|
|
#endif
|
|
|
|
// Determine size of header and footer
|
|
int header_size = sizeof(ZoneHeader);
|
|
int align_size = Z_GetAlign(header);
|
|
int footer_size = 0;
|
|
int data_size = Z_GetSize(header);
|
|
if (Z_IsTagLinked(tag))
|
|
{
|
|
header_size += sizeof(ZoneLinkHeader);
|
|
}
|
|
if (Z_IsTagTemp(tag))
|
|
{
|
|
footer_size += align_size;
|
|
}
|
|
else
|
|
{
|
|
header_size += align_size;
|
|
}
|
|
#ifdef _DEBUG
|
|
header_size += sizeof(ZoneDebugHeader);
|
|
footer_size += sizeof(ZoneDebugFooter);
|
|
#endif
|
|
int real_size = data_size + header_size + footer_size;
|
|
|
|
// Update the stats
|
|
s_Stats.m_SizeAlloc -= data_size;
|
|
s_Stats.m_OverheadAlloc -= header_size + footer_size;
|
|
s_Stats.m_SizesPerTag[tag] -= data_size;
|
|
s_Stats.m_CountAlloc--;
|
|
s_Stats.m_CountsPerTag[tag]--;
|
|
|
|
// Delink block
|
|
if (Z_IsTagLinked(tag))
|
|
{
|
|
ZoneLinkHeader* linked = (ZoneLinkHeader*)header - 1;
|
|
|
|
if (linked == s_LinkBase)
|
|
{
|
|
s_LinkBase = linked->m_Next;
|
|
if (s_LinkBase)
|
|
{
|
|
s_LinkBase->m_Prev = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (linked->m_Next)
|
|
{
|
|
linked->m_Next->m_Prev = linked->m_Prev;
|
|
}
|
|
linked->m_Prev->m_Next = linked->m_Next;
|
|
}
|
|
|
|
assert(Z_ValidateLinks());
|
|
}
|
|
|
|
// Clear the block header for safety
|
|
*header = 0;
|
|
|
|
// Add block to free list
|
|
ZoneFreeBlock* nblock = NULL;
|
|
if (real_size < sizeof(ZoneFreeBlock))
|
|
{
|
|
// Not enough space in block to put free information --
|
|
// use overflow buffer.
|
|
nblock = Z_GetOverflowBlock();
|
|
|
|
if (nblock == NULL)
|
|
{
|
|
Z_Details_f();
|
|
Com_Error(ERR_FATAL, "Zone free overflow buffer overflowed!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Place free information in block
|
|
nblock = (ZoneFreeBlock*)((char*)pvAddress - header_size);
|
|
}
|
|
|
|
nblock->m_Address = (unsigned int)pvAddress - header_size;
|
|
nblock->m_Size = real_size;
|
|
Z_LinkFreeBlock(nblock);
|
|
|
|
// Coalesce any adjacent free blocks
|
|
Z_Coalasce(nblock);
|
|
#ifndef _GAMECUBE
|
|
ReleaseMutex(s_Mutex);
|
|
#endif
|
|
}
|
|
|
|
Z_FREE_RETURN( 0 );
|
|
}
|
|
|
|
|
|
int Z_MemSize(memtag_t eTag)
|
|
{
|
|
return s_Stats.m_SizesPerTag[eTag];
|
|
}
|
|
|
|
#if ZONE_DEBUG
|
|
void Z_FindLeak(void)
|
|
{
|
|
assert(s_Initialized);
|
|
|
|
static int cycle_count = 0;
|
|
const memtag_t tag = TAG_NEWDEL;
|
|
|
|
struct PointerInfo
|
|
{
|
|
void* data;
|
|
int counter;
|
|
bool mark;
|
|
};
|
|
|
|
const int max_pointers = 32768;
|
|
static PointerInfo pointers[max_pointers];
|
|
static int num_pointers = 0;
|
|
|
|
// Clear pointer existance
|
|
for (int i = 0; i < num_pointers; ++i)
|
|
{
|
|
pointers[i].mark = false;
|
|
}
|
|
|
|
// Add all known pointers
|
|
int start_num = num_pointers;
|
|
for (ZoneLinkHeader* link = s_LinkBase; link;)
|
|
{
|
|
ZoneHeader* header = (ZoneHeader*)(link + 1);
|
|
link = link->m_Next;
|
|
|
|
if (Z_GetTag(header) == tag)
|
|
{
|
|
// See if the pointer already is in the array
|
|
bool found = false;
|
|
for (int k = start_num; k < num_pointers; ++k)
|
|
{
|
|
if (pointers[k].data == header)
|
|
{
|
|
++pointers[k].counter;
|
|
pointers[k].mark = true;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the pointer is not in the array, add it
|
|
if (!found)
|
|
{
|
|
assert(num_pointers < max_pointers);
|
|
pointers[num_pointers].data = header;
|
|
pointers[num_pointers].counter = 0;
|
|
pointers[num_pointers].mark = true;
|
|
++num_pointers;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove pointers that are no longer used
|
|
for (int j = 0; j < num_pointers; ++j)
|
|
{
|
|
if (pointers[j].mark)
|
|
{
|
|
if (pointers[j].counter != cycle_count &&
|
|
pointers[j].counter != cycle_count - 1 &&
|
|
pointers[j].counter != 0)
|
|
{
|
|
Com_Printf("Memory leak: %p\n", pointers[j].data);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int k;
|
|
for (k = j; k < num_pointers; ++k)
|
|
{
|
|
if (pointers[k].mark) break;
|
|
}
|
|
|
|
if (k == num_pointers) break;
|
|
|
|
memmove(pointers + j, pointers + k, (num_pointers - k) * sizeof(PointerInfo));
|
|
num_pointers -= k - j;
|
|
}
|
|
}
|
|
|
|
++cycle_count;
|
|
}
|
|
#endif
|
|
|
|
void Z_TagPointers(memtag_t eTag)
|
|
{
|
|
assert(s_Initialized);
|
|
|
|
#ifndef _GAMECUBE
|
|
WaitForSingleObject(s_Mutex, INFINITE);
|
|
#endif
|
|
|
|
Com_Printf("Pointers for tag %d:\n", eTag);
|
|
|
|
for (ZoneLinkHeader* link = s_LinkBase; link;)
|
|
{
|
|
ZoneHeader* header = (ZoneHeader*)(link + 1);
|
|
link = link->m_Next;
|
|
|
|
if (eTag == TAG_ALL || Z_GetTag(header) == eTag)
|
|
{
|
|
#ifdef _DEBUG
|
|
Com_Printf("%x - %d\n", ((void*)((char*)header +
|
|
sizeof(ZoneHeader) + sizeof(ZoneDebugHeader))),
|
|
Z_Size(((void*)((char*)header +
|
|
sizeof(ZoneHeader) + sizeof(ZoneDebugHeader)))));
|
|
#else
|
|
Com_Printf("%x - %d\n", (void*)(header + 1),
|
|
Z_Size((void*)(header + 1)));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#ifndef _GAMECUBE
|
|
ReleaseMutex(s_Mutex);
|
|
#endif
|
|
}
|
|
|
|
void Z_TagFree(memtag_t eTag)
|
|
{
|
|
assert(s_Initialized);
|
|
|
|
for (ZoneLinkHeader* link = s_LinkBase; link;)
|
|
{
|
|
ZoneHeader* header = (ZoneHeader*)(link + 1);
|
|
link = link->m_Next;
|
|
|
|
if (eTag == TAG_ALL || Z_GetTag(header) == eTag)
|
|
{
|
|
#ifdef _DEBUG
|
|
Z_Free((void*)((char*)header + sizeof(ZoneHeader) +
|
|
sizeof(ZoneDebugHeader)));
|
|
#else
|
|
Z_Free((void*)(header + 1));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void Z_SetNewDeleteTemporary(bool bTemp)
|
|
{
|
|
// Catch nested uses that break when unwinding the stack
|
|
assert(bTemp != s_IsNewDeleteTemp);
|
|
s_IsNewDeleteTemp = bTemp;
|
|
}
|
|
|
|
void *S_Malloc( int iSize )
|
|
{
|
|
return Z_Malloc(iSize, TAG_SMALL, qfalse, 0);
|
|
}
|
|
|
|
int Z_GetLevelMemory(void)
|
|
{
|
|
#ifdef _JK2MP
|
|
return s_Stats.m_SizesPerTag[TAG_BSP];
|
|
#else
|
|
return s_Stats.m_SizesPerTag[TAG_HUNKALLOC] +
|
|
s_Stats.m_SizesPerTag[TAG_HUNKMISCMODELS] +
|
|
s_Stats.m_SizesPerTag[TAG_BSP];
|
|
#endif
|
|
}
|
|
|
|
#ifdef _JK2MP
|
|
int Z_GetHunkMemory(void)
|
|
{
|
|
return s_Stats.m_SizesPerTag[TAG_HUNK_MARK1] +
|
|
s_Stats.m_SizesPerTag[TAG_HUNK_MARK2] +
|
|
s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC];
|
|
}
|
|
#endif
|
|
|
|
int Z_GetTerrainMemory(void)
|
|
{
|
|
return s_Stats.m_SizesPerTag[TAG_CM_TERRAIN] +
|
|
s_Stats.m_SizesPerTag[TAG_CM_TERRAIN_TEMP] +
|
|
#ifdef _JK2MP
|
|
s_Stats.m_SizesPerTag[TAG_TERRAIN] +
|
|
#endif
|
|
s_Stats.m_SizesPerTag[TAG_R_TERRAIN];
|
|
}
|
|
|
|
int Z_GetMiscMemory(void)
|
|
{
|
|
return s_Stats.m_SizeAlloc -
|
|
(Z_GetLevelMemory() +
|
|
#ifdef _JK2MP
|
|
Z_GetHunkMemory() +
|
|
#endif
|
|
Z_GetTerrainMemory() +
|
|
s_Stats.m_SizesPerTag[TAG_MODEL_GLM] +
|
|
s_Stats.m_SizesPerTag[TAG_MODEL_GLA] +
|
|
s_Stats.m_SizesPerTag[TAG_MODEL_MD3] +
|
|
s_Stats.m_SizesPerTag[TAG_BINK] +
|
|
s_Stats.m_SizesPerTag[TAG_SND_RAWDATA]);
|
|
}
|
|
|
|
#ifdef _GAMECUBE
|
|
static int texMemSize = 0;
|
|
#else
|
|
extern int texMemSize;
|
|
#endif
|
|
void Z_CompactStats(void)
|
|
{
|
|
assert(s_Initialized);
|
|
|
|
//This report is conservative. Divides by 1000 instead of 1024 and
|
|
//then rounds up.
|
|
Sys_Log("memory-map.txt", va("**Z_CompactStats Start**\n"));
|
|
Sys_Log("memory-map.txt", va("map: %s\n", Cvar_VariableString( "mapname" )) );
|
|
|
|
Sys_Log("memory-map.txt", va("OV: %d, LVL: %d, GLM: %d, GLA: %d, MD3: %d\n",
|
|
(s_Stats.m_OverheadAlloc / 1000) + 1,
|
|
(Z_GetLevelMemory() / 1000) + 1,
|
|
(s_Stats.m_SizesPerTag[TAG_MODEL_GLM] / 1000) + 1,
|
|
(s_Stats.m_SizesPerTag[TAG_MODEL_GLA] / 1000) + 1,
|
|
(s_Stats.m_SizesPerTag[TAG_MODEL_MD3] / 1000) + 1));
|
|
|
|
Sys_Log("memory-map.txt", va("TER: %d, SND: %d, TEX: %d, FMV: %d, MSC: %d\n",
|
|
(Z_GetTerrainMemory() / 1000) + 1,
|
|
(s_Stats.m_SizesPerTag[TAG_SND_RAWDATA] / 1000) + 1,
|
|
(texMemSize / 1000) + 1,
|
|
(s_Stats.m_SizesPerTag[TAG_BINK] / 1000) + 1,
|
|
(Z_GetMiscMemory() / 1000) + 1));
|
|
|
|
#ifdef _JK2MP
|
|
Sys_Log("memory-map.txt", va("HUNK: %d, THUNK: %d\n",
|
|
((s_Stats.m_SizesPerTag[TAG_HUNK_MARK1] + s_Stats.m_SizesPerTag[TAG_HUNK_MARK2]) / 1000) + 1,
|
|
(s_Stats.m_SizesPerTag[TAG_TEMP_HUNKALLOC] / 1000) + 1));
|
|
#endif
|
|
|
|
Sys_Log("memory-map.txt", va("Free Zone: %d\n", s_Stats.m_SizeFree));
|
|
|
|
}
|
|
|
|
|
|
static void Z_Stats_f(void)
|
|
{
|
|
assert(s_Initialized);
|
|
// Display some memory usage summary information...
|
|
|
|
Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n",
|
|
s_Stats.m_SizeAlloc,
|
|
(float)s_Stats.m_SizeAlloc / 1024.0f / 1024.0f,
|
|
s_Stats.m_CountAlloc);
|
|
|
|
Com_Printf("Free memory is %d bytes (%.2fMB) in %d memory blocks\n",
|
|
s_Stats.m_SizeFree,
|
|
(float)s_Stats.m_SizeFree / 1024.0f / 1024.0f,
|
|
s_Stats.m_CountFree);
|
|
|
|
Com_Printf("The zone peaked at %d bytes (%.2fMB)\n",
|
|
s_Stats.m_PeakAlloc,
|
|
(float)s_Stats.m_PeakAlloc / 1024.0f / 1024.0f);
|
|
|
|
Com_Printf("The zone overhead is %d bytes (%.2fMB)\n",
|
|
s_Stats.m_OverheadAlloc,
|
|
(float)s_Stats.m_OverheadAlloc / 1024.0f / 1024.0f);
|
|
}
|
|
|
|
void Z_Details_f(void)
|
|
{
|
|
assert(s_Initialized);
|
|
// Display some tag specific information...
|
|
|
|
Com_Printf("---------------------------------------------------------------------------\n");
|
|
Com_Printf("%20s %9s\n","Zone Tag","Bytes");
|
|
Com_Printf("%20s %9s\n","--------","-----");
|
|
for (int i=0; i<TAG_COUNT; i++)
|
|
{
|
|
int iThisCount = s_Stats.m_CountsPerTag[i];
|
|
int iThisSize = s_Stats.m_SizesPerTag[i];
|
|
|
|
if (iThisCount)
|
|
{
|
|
float fSize = (float)(iThisSize) / 1024.0f / 1024.0f;
|
|
int iSize = fSize;
|
|
int iRemainder = 100.0f * (fSize - floor(fSize));
|
|
Com_Printf("%d %9d (%2d.%02dMB) in %6d blocks (%9d average)\n",
|
|
i, iThisSize, iSize, iRemainder, iThisCount, iThisSize / iThisCount);
|
|
}
|
|
}
|
|
Com_Printf("---------------------------------------------------------------------------\n");
|
|
|
|
Z_Stats_f();
|
|
}
|
|
|
|
void Z_DumpMemMap_f(void)
|
|
{
|
|
# define WRITECHAR(C) \
|
|
Sys_Log("memmap.txt", C, 1, false); \
|
|
cur += 1024; \
|
|
if ((++counter) % 81 == 0) Sys_Log("memmap.txt", "\n", 1, false);
|
|
|
|
unsigned int cur = (unsigned int)s_PoolBase;
|
|
unsigned int counter = 0;
|
|
for (ZoneFreeBlock* fblock = &s_FreeStart; fblock != &s_FreeEnd; fblock = fblock->m_Next)
|
|
{
|
|
while (fblock->m_Address > cur + 1024)
|
|
{
|
|
WRITECHAR("*");
|
|
}
|
|
|
|
if (fblock->m_Address > cur && fblock->m_Address < cur + 1024)
|
|
{
|
|
WRITECHAR("+");
|
|
}
|
|
|
|
while (fblock->m_Address + fblock->m_Size > cur + 1024)
|
|
{
|
|
WRITECHAR("-");
|
|
}
|
|
|
|
if (fblock->m_Address + fblock->m_Size > cur &&
|
|
fblock->m_Address + fblock->m_Size < cur + 1024)
|
|
{
|
|
WRITECHAR("+");
|
|
}
|
|
}
|
|
|
|
Sys_Log("memmap.txt", "\n");
|
|
}
|
|
|
|
void Z_DisplayLevelMemory(int size, int surf, int block)
|
|
{
|
|
Z_DumpMemMap_f();
|
|
|
|
//Yes, it should be divided by 1024, but I'm going for a safety margin
|
|
//by rounding down.
|
|
//Com_Printf("level memory used: %d KB\n", size / 1000);
|
|
//Z_CompactStats(size, surf, block);
|
|
Z_CompactStats();
|
|
}
|
|
|
|
void Z_DisplayLevelMemory(void)
|
|
{
|
|
#ifdef _GAMECUBE
|
|
extern void R_SurfMramUsed(int &surface, int &block);
|
|
int surface, block;
|
|
R_SurfMramUsed(surface, block);
|
|
Z_DisplayLevelMemory(Z_GetLevelMemory(), surface, block);
|
|
#else
|
|
Z_DisplayLevelMemory(Z_GetLevelMemory(), 0, 0);
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
========================
|
|
CopyString
|
|
|
|
NOTE: never write over the memory CopyString returns because
|
|
memory from a memstatic_t might be returned
|
|
========================
|
|
*/
|
|
char *CopyString( const char *in )
|
|
{
|
|
struct ZoneSingleChar
|
|
{
|
|
ZoneHeader header;
|
|
#ifdef _DEBUG
|
|
ZoneDebugHeader start;
|
|
#endif
|
|
char data[2];
|
|
#ifdef _DEBUG
|
|
ZoneDebugFooter end;
|
|
#endif
|
|
};
|
|
|
|
#ifdef _DEBUG
|
|
static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, ZONE_MAGIC, "\0", ZONE_MAGIC};
|
|
static ZoneSingleChar numbers[10] =
|
|
{
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "0", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "1", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "2", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "3", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "4", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "5", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "6", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "7", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "8", ZONE_MAGIC},
|
|
{(TAG_STATIC << 25) | 2, ZONE_MAGIC, "9", ZONE_MAGIC},
|
|
};
|
|
#else
|
|
static ZoneSingleChar empty = {(TAG_STATIC << 25) | 2, "\0"};
|
|
static ZoneSingleChar numbers[10] =
|
|
{
|
|
{(TAG_STATIC << 25) | 2, "0"},
|
|
{(TAG_STATIC << 25) | 2, "1"},
|
|
{(TAG_STATIC << 25) | 2, "2"},
|
|
{(TAG_STATIC << 25) | 2, "3"},
|
|
{(TAG_STATIC << 25) | 2, "4"},
|
|
{(TAG_STATIC << 25) | 2, "5"},
|
|
{(TAG_STATIC << 25) | 2, "6"},
|
|
{(TAG_STATIC << 25) | 2, "7"},
|
|
{(TAG_STATIC << 25) | 2, "8"},
|
|
{(TAG_STATIC << 25) | 2, "9"},
|
|
};
|
|
#endif
|
|
|
|
char *out;
|
|
|
|
if (!in[0])
|
|
{
|
|
return empty.data;
|
|
}
|
|
else if (!in[1])
|
|
{
|
|
if (in[0] >= '0' && in[0] <= '9')
|
|
{
|
|
return numbers[in[0]-'0'].data;
|
|
}
|
|
}
|
|
|
|
out = (char *) S_Malloc (strlen(in)+1);
|
|
strcpy (out, in);
|
|
|
|
// Z_Label(out,in);
|
|
|
|
return out;
|
|
}
|
|
|
|
void Com_TouchMemory(void)
|
|
{
|
|
// Stub function. Do nothing.
|
|
return;
|
|
}
|
|
|
|
qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag)
|
|
{
|
|
assert(s_Initialized);
|
|
|
|
#ifdef _DEBUG
|
|
ZoneDebugHeader* debug = (ZoneDebugHeader*)pvAddress - 1;
|
|
|
|
if (*debug != ZONE_MAGIC)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
pvAddress = (void*)debug;
|
|
#endif
|
|
|
|
ZoneHeader* header = (ZoneHeader*)pvAddress - 1;
|
|
|
|
if (Z_GetTag(header) != eTag)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
return Z_GetSize(header);
|
|
}
|
|
|
|
|
|
/*
|
|
Hunk emulation - PC switched to system similar to ours. I made the remaining
|
|
changes so that the two are identical.
|
|
*/
|
|
#ifdef _JK2MP
|
|
|
|
qboolean Com_TheHunkMarkHasBeenMade(void)
|
|
{
|
|
if (hunk_tag == TAG_HUNK_MARK2)
|
|
{
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Com_InitHunkMemory
|
|
=================
|
|
*/
|
|
void Com_InitHunkMemory(void)
|
|
{
|
|
hunk_tag = TAG_HUNK_MARK1;
|
|
Hunk_Clear();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
Hunk_MemoryRemaining
|
|
====================
|
|
*/
|
|
int Hunk_MemoryRemaining(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
Hunk_SetMark
|
|
|
|
The server calls this after the level and game VM have been loaded
|
|
===================
|
|
*/
|
|
void Hunk_SetMark(void)
|
|
{
|
|
hunk_tag = TAG_HUNK_MARK2;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Hunk_ClearToMark
|
|
|
|
The client calls this before starting a vid_restart or snd_restart
|
|
=================
|
|
*/
|
|
void Hunk_ClearToMark(void)
|
|
{
|
|
assert(hunk_tag == TAG_HUNK_MARK2); //if this is not true then no mark has been made
|
|
Z_TagFree(TAG_HUNK_MARK2);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Hunk_CheckMark
|
|
=================
|
|
*/
|
|
qboolean Hunk_CheckMark( void )
|
|
{
|
|
if (hunk_tag != TAG_HUNK_MARK1)
|
|
{
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Hunk_Clear
|
|
|
|
The server calls this before shutting down or loading a new map
|
|
VVFIXME - PC version does lots of other things in here.
|
|
=================
|
|
*/
|
|
void R_HunkClearCrap(void);
|
|
void Hunk_Clear(void)
|
|
{
|
|
hunk_tag = TAG_HUNK_MARK1;
|
|
Z_TagFree(TAG_HUNK_MARK1);
|
|
Z_TagFree(TAG_HUNK_MARK2);
|
|
|
|
R_HunkClearCrap();
|
|
/*
|
|
Z_TagFree(TAG_HUNKALLOC);
|
|
Z_TagFree(TAG_BSP_HUNK);
|
|
Z_TagFree(TAG_BOT_HUNK);
|
|
Z_TagFree(TAG_RENDERER_HUNK);
|
|
Z_TagFree(TAG_SKELETON);
|
|
Z_TagFree(TAG_MODEL_OTHER);
|
|
Z_TagFree(TAG_MODEL_CHAR);
|
|
VM_Clear();
|
|
*/
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Hunk_Alloc
|
|
|
|
Allocate permanent (until the hunk is cleared) memory
|
|
=================
|
|
*/
|
|
void *Hunk_Alloc(int size, ha_pref preference)
|
|
{
|
|
return Z_Malloc(size, hunk_tag, qtrue);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Hunk_AllocateTempMemory
|
|
|
|
This is used by the file loading system.
|
|
Multiple files can be loaded in temporary memory.
|
|
When the files-in-use count reaches zero, all temp memory will be deleted
|
|
=================
|
|
*/
|
|
void *Hunk_AllocateTempMemory(int size)
|
|
{
|
|
// don't bother clearing, because we are going to load a file over it
|
|
return Z_Malloc(size, TAG_TEMP_HUNKALLOC, qfalse);
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Hunk_FreeTempMemory
|
|
==================
|
|
*/
|
|
void Hunk_FreeTempMemory(void *buf)
|
|
{
|
|
Z_Free(buf);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Hunk_ClearTempMemory
|
|
|
|
The temp space is no longer needed. If we have left more
|
|
touched but unused memory on this side, have future
|
|
permanent allocs use this side.
|
|
=================
|
|
*/
|
|
void Hunk_ClearTempMemory(void)
|
|
{
|
|
Z_TagFree(TAG_TEMP_HUNKALLOC);
|
|
}
|
|
|
|
#endif // _JK2MP
|
|
|
|
/*
|
|
XTL Replacement functions
|
|
XMemAlloc
|
|
XMemFree
|
|
XMemSize
|
|
|
|
Replacing these lets us intercept ALL memory allocation done by the XTL, and lets the
|
|
Zone take pretty much all available memory at startup
|
|
*/
|
|
/* This still doesn't work. Numrous allocations still use internal functions, so there's
|
|
little benefit right now.
|
|
|
|
XBOXAPI
|
|
LPVOID
|
|
WINAPI
|
|
XMemAlloc(SIZE_T dwSize, DWORD dwAllocAttributes)
|
|
{
|
|
// We always give XTL 16 byte aligned memory
|
|
return Z_Malloc(dwSize, TAG_XTL, ((PXALLOC_ATTRIBUTES)&dwAllocAttributes)->dwZeroInitialize, 16);
|
|
}
|
|
|
|
XBOXAPI
|
|
VOID
|
|
WINAPI
|
|
XMemFree(PVOID pAddress, DWORD dwAllocAttributes)
|
|
{
|
|
Z_Free(pAddress);
|
|
}
|
|
|
|
XBOXAPI
|
|
SIZE_T
|
|
WINAPI
|
|
XMemSize(PVOID pAddress, DWORD dwAllocAttributes)
|
|
{
|
|
return Z_Size(pAddress);
|
|
}
|
|
|
|
*/
|