/******************************Module*Header*******************************\ * * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * !! !! * !! WARNING: NOT DDK SAMPLE CODE !! * !! !! * !! This source code is provided for completeness only and should not be !! * !! used as sample code for display driver development. Only those sources !! * !! marked as sample code for a given driver component should be used for !! * !! development purposes. !! * !! !! * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * * Module Name: linalloc.c * * Content: Videomemory linear allocator * * Copyright (c) 1995-2003 Microsoft Corporation \**************************************************************************/ #include "glint.h" //----------------------------------------------------------------------------- // // This module implements video memory allocation. It isn't a great // allocator (though it IS robust), but mainly it shows how to hook // up your own if you need/wish to. // //----------------------------------------------------------------------------- // In linalloc.h we define MEMORY_MAP_SIZE and LinearAllocatorInfo // which are key for our implementation // This define allows allocations to search more efficiently for free // memory. If you want to keep things simple you can turn it off // and things will still work fine. #define ALLOC_OPTIMIZE 1 // Total number of chunks per element of our memory map array // (which is of DWORD type , therefore we use sizeof(DWORD) ) #define CHUNKS_PER_ELEM (sizeof(DWORD)*8) // Memory to be managed will be subdivided in "memory chunks". Each memory // chunk status will be signaled by a bit in the memory map by being turned // on or off. #define TOTAL_MEM_CHUNKS (MEMORY_MAP_SIZE * CHUNKS_PER_ELEM) // Macros to set, clear and test the value of a given chunk bit without // worrying about the structure internals. #define CHUNKNUM_BIT(chunk_num) \ (1 << ((chunk_num) % CHUNKS_PER_ELEM)) #define CHUNKNUM_ELEM(mmap, chunk_num) \ mmap[ (chunk_num) / CHUNKS_PER_ELEM ] #define SET_MEM_CHUNK(mmap, chunk_num) \ CHUNKNUM_ELEM(mmap, chunk_num) |= CHUNKNUM_BIT(chunk_num) #define CLR_MEM_CHUNK(mmap, chunk_num) \ CHUNKNUM_ELEM(mmap, chunk_num) &= ~CHUNKNUM_BIT(chunk_num) #define MEM_CHUNK_VAL(mmap, chunk_num) \ ((CHUNKNUM_ELEM(mmap, chunk_num) & CHUNKNUM_BIT(chunk_num)) > 0 ? 1 : 0) // Macros that do the mapping between real (heap) memory pointers and the // chunking indices. #define MEM_BYTES_TO_CHUNKS(pAlloc, dwBytes) \ ( (dwBytes) / pAlloc->dwMemPerChunk + \ ( ((dwBytes) % pAlloc->dwMemPerChunk)? 1 : 0 ) \ ) #define CHUNK_NUM_TO_PTR(pAlloc, num) \ ( (num) * pAlloc->dwMemPerChunk + pAlloc->dwMemStart ) #define MEM_PTR_TO_CHUNK_NUM(pAlloc, ptr) \ MEM_BYTES_TO_CHUNKS(pAlloc, ((ptr) - pAlloc->dwMemStart) ) //----------------------------------------------------------------------------- // // __LIN_AlignPtr // // Return an aligned pointer // //----------------------------------------------------------------------------- DWORD __LIN_AlignPtr(DWORD pointer, DWORD alignment) { ULONG ulExtraBytes; ulExtraBytes = pointer % alignment; if (ulExtraBytes == 0) { ulExtraBytes = alignment; } // add enough to pointer so that its new value % alignment is == 0 return (pointer + alignment - ulExtraBytes); } // __LIN_AlignPtr //----------------------------------------------------------------------------- // // __LIN_CalcMaxChunks // // Calculate the number of chunks in the heap // //----------------------------------------------------------------------------- void __LIN_CalcMaxChunks(LinearAllocatorInfo* pAlloc) { DWORD n, dwSizeHeap; // Compute how many chunks we'll need and what size of heap each // chunk will control for this linear allocator. dwSizeHeap = pAlloc->dwMemEnd - pAlloc->dwMemStart; // We will need dwMemPerChunk * dwMaxChunks to be >= dwSizeHeap. // We also want dwMaxChunks to be as close to TOTAL_MEM_CHUNKS and // we would like (though its not necessary) dwMemPerChunk to be as 2^N. // (and making them at least 16 bytes makes life easier for // the alignment requirements we have in this driver). for(n = 4; n < 32; n++) { // our current choice of heap size each chunk will control pAlloc->dwMemPerChunk = 1 << n; // 2^N // how many chunks do we need for such case? pAlloc->dwMaxChunks = dwSizeHeap / pAlloc->dwMemPerChunk; if (dwSizeHeap % pAlloc->dwMemPerChunk != 0) { pAlloc->dwMaxChunks++; } // can we accept this result to fit in our data structure? if (pAlloc->dwMaxChunks <= TOTAL_MEM_CHUNKS) { // We have as finely grained chunks as we can without // exceeding our self imposed limits. break; } } // 1 << n is the size of 1 chunk which is 1k on P3 with 256MB video memory ASSERTDD((n < 32), "__LIN_CalcMaxChunks : Wrong heap size"); } //----------------------------------------------------------------------------- // // __LIN_ReInitWhenNeeded // // Reinitialize heap allocater if needed. This is important only for // the Win9x driver which can signal us from the 16bit side in a mode // change that it needs the heap to be reinitialized completely. (It will // do this by simple setting bResetLinAllocator to TRUE). // //----------------------------------------------------------------------------- void __LIN_ReInitWhenNeeded(LinearAllocatorInfo* pAlloc) { #ifdef W95_DDRAW if (pAlloc) { if (pAlloc->bResetLinAllocator) { // Clean all previous allocation data in the memory map if (pAlloc->pMMap) { memset(pAlloc->pMMap, 0, sizeof(MemoryMap)); } // Clean all previous lenght data in the memory map if (pAlloc->pLenMap) { memset(pAlloc->pLenMap, 0, sizeof(MemoryMap)); } // Recalculate max chunks due to change of heap's size __LIN_CalcMaxChunks(pAlloc); } // reinitialization completed pAlloc->bResetLinAllocator = FALSE; } #endif // W95_DDRAW } // __LIN_ReInitWhenNeeded //----------------------------------------------------------------------------- // // _DX_LIN_InitialiseHeapManager // // Creates the heap manager. This code is fairly common to this // sample app and the dd allocator as it will stand. The operations // it performs will be in perm3dd and/or mini, though the shared heap // memory can be allocated from 16 and 32 bit land. // //----------------------------------------------------------------------------- BOOL _DX_LIN_InitialiseHeapManager(LinearAllocatorInfo* pAlloc, DWORD dwMemStart, DWORD dwMemEnd) { DWORD n; // Reinitialize heap allocater if needed __LIN_ReInitWhenNeeded(pAlloc); pAlloc->dwMemStart = dwMemStart; pAlloc->dwMemEnd = dwMemEnd; pAlloc->bResetLinAllocator = FALSE; // Get memory for the allocator's memory map pAlloc->pMMap = (MemoryMap*)HEAP_ALLOC(HEAP_ZERO_MEMORY, sizeof(MemoryMap), ALLOC_TAG_DX(G)); if(pAlloc->pMMap == NULL) { // Out of memory return FALSE; } // Clear the memory map memset(pAlloc->pMMap, 0, sizeof(MemoryMap)); // Calculate the maximum number of chunks __LIN_CalcMaxChunks(pAlloc); // Get memory for the allocator's lenght memory map. We'll keep here // a map of 0's and 1's where 1 will indicate where the current // allocated block ends. That way we won't need to keep any binding // between the allocated addresses and the size of each one in order // to do the right thing when we are asked to free the memory pAlloc->pLenMap = (MemoryMap*)HEAP_ALLOC(HEAP_ZERO_MEMORY, sizeof(MemoryMap), ALLOC_TAG_DX(H)); if(pAlloc->pLenMap == NULL) { // Couln't allocate the lenght map, deallocate the memory map HEAP_FREE(pAlloc->pMMap); pAlloc->pMMap = NULL; // Out of memory return FALSE; } // Clear the lenghts memory map memset(pAlloc->pLenMap, 0xFF, sizeof(MemoryMap)); return TRUE; } // _DX_LIN_InitialiseHeapManager //----------------------------------------------------------------------------- // // _DX_LIN_UnInitialiseHeapManager(pLinearAllocatorInfo pAlloc) // // Frees the heap manager. This code is fairly common to this // sample app and the dd allocator as it will stand. The operations // it performs will be in p3r3dx and/or mini, though the shared heap // memory can be allocated from 16 and 32 bit land. // //----------------------------------------------------------------------------- void _DX_LIN_UnInitialiseHeapManager(LinearAllocatorInfo* pAlloc) { __LIN_ReInitWhenNeeded(pAlloc); // Destroy/Clean all previous allocation data if (pAlloc) { if(pAlloc->pMMap) { HEAP_FREE(pAlloc->pMMap); pAlloc->pMMap = NULL; } if(pAlloc->pLenMap) { HEAP_FREE(pAlloc->pLenMap); pAlloc->pLenMap = NULL; } } } // _DX_LIN_UnInitialiseHeapManager //----------------------------------------------------------------------------- // // _DX_LIN_AllocateLinearMemory // // This is the allocation interface to the allocator. It gives an // application the opportunity to allocate a linear chunk of memory // //----------------------------------------------------------------------------- DWORD _DX_LIN_AllocateLinearMemory( pLinearAllocatorInfo pAlloc, LPMEMREQUEST lpMemReq) { INT i; DWORD dwBytes, dwCurrStartChunk, dwCurrEndChunk, dwNumContChunksFound, dwContChunksNeeded; #if ALLOC_OPTIMIZE // Each block is CHUNKS_PER_ELE chuncks DWORD dwStartLastBlock; #endif // Reinitialize heap allocater if needed __LIN_ReInitWhenNeeded(pAlloc); // Validate the passed data if ((lpMemReq == NULL) || (lpMemReq->dwSize != sizeof(P3_MEMREQUEST))) { DISPDBG((ERRLVL,"ERROR: NULL lpMemReq passed!")); return GLDD_INVALIDARGS; } if ((!pAlloc) || (pAlloc->pMMap == NULL) || (pAlloc->pLenMap == NULL) ) { DISPDBG((ERRLVL,"ERROR: invalid pAlloc passed!")); return GLDD_INVALIDARGS; } // Always ensure that alignment is a DWORD (or DWORD multiple) if (lpMemReq->dwAlign < 4) { lpMemReq->dwAlign = 4; } while ((lpMemReq->dwAlign % 4) != 0) { lpMemReq->dwAlign++; } // Always align memory requests to a minimum of a 4 byte boundary dwBytes = __LIN_AlignPtr(lpMemReq->dwBytes, lpMemReq->dwAlign); if (dwBytes == 0) { DISPDBG((WRNLVL,"ERROR: Requested 0 Bytes!")); return GLDD_INVALIDARGS; } // Determine how many chunks of memory we'll need to allocate dwContChunksNeeded = MEM_BYTES_TO_CHUNKS(pAlloc, dwBytes); // We don't check if we were called with MEM3DL_FIRST_FIT since // that's the only thing we know how to do right now. We decide // whether we'll search from back to front or viceversa. We will // scan the memory map in the chosen direction looking for a "hole" // large enough for the current request. if (lpMemReq->dwFlags & MEM3DL_BACK) { // We will examine the MemoryMap from the end towards the front // looking out for a suitable space with the required number of // chunks we need dwCurrEndChunk = 0; dwNumContChunksFound = 0; for ( i = pAlloc->dwMaxChunks - 1; i >= 0 ; i--) { #if ALLOC_OPTIMIZE // we are about to start testing a specific DWORD in // the memory map (going from the end to the start) if (( i % 32) == 31) { // If the whole DWORD is 0xFFFFFFFF (meaning all chunks are // already allocated) then we can & should skip it altogheter while ((i >= 0) && (CHUNKNUM_ELEM((*pAlloc->pMMap), i) == 0xFFFFFFFF)) { // Search needs to be restarted dwNumContChunksFound = 0; i -= 32; } // If the whole DWORD is 0x00000000 (meaning none of the // chunks is yet allocated) then we could grab all while ((i >= 0) && (CHUNKNUM_ELEM((*pAlloc->pMMap), i) == 0x00000000) && !(dwNumContChunksFound >= dwContChunksNeeded)) { if (dwNumContChunksFound == 0) { dwCurrEndChunk = i; } i -= 32; dwNumContChunksFound += 32; } if (dwNumContChunksFound >= dwContChunksNeeded) { // We've found a suitable place! Figure out where it starts dwCurrStartChunk = dwCurrEndChunk - dwContChunksNeeded + 1; break; } else if(!(i >= 0)) { break; // finished examining all memory, break loop here } } #endif // ALLOC_OPTIMIZE if (MEM_CHUNK_VAL((*pAlloc->pMMap), i ) == 0) { if (dwNumContChunksFound == 0) { // our count so far of contigous chunks is zero, // meaning that were just starting to find free // chunks. We need to remember where this block // is ending dwCurrEndChunk = i; } dwNumContChunksFound++; } else { // This chunk is being used and we haven't found a suitable // set of chunks, so reset our count of contigous chunks // found so far dwNumContChunksFound = 0; } if (dwNumContChunksFound >= dwContChunksNeeded) { // We've found a suitable place! Figure out where it starts. dwCurrStartChunk = dwCurrEndChunk - dwContChunksNeeded + 1; break; // break loop here } } } else // even if no flags are set lets allocate at the heaps front { // We will examine the MemoryMap from the front towards the end // looking out for a suitable space with the required number of // chunks we need dwCurrStartChunk = 0; dwNumContChunksFound = 0; #if ALLOC_OPTIMIZE // At the end of the heap there might be a region smaller than // CHUNKS_PER_ELEM(32) of chunks, and optimized search of 32 // chunk free blocks should be disabled in that region. dwStartLastBlock = (pAlloc->dwMaxChunks / CHUNKS_PER_ELEM) * CHUNKS_PER_ELEM; #endif for ( i = 0 ; i < (INT)pAlloc->dwMaxChunks ; i++) { #if ALLOC_OPTIMIZE // we are about to start testing a specific // DWORD in the memory map. if (( i % 32) == 0) { // If the whole DWORD is 0xFFFFFFFF (meaning all chunks are // already allocated) then we can & should skip it altogheter while ((i < (INT)dwStartLastBlock) && (CHUNKNUM_ELEM((*pAlloc->pMMap), i) == 0xFFFFFFFF)) { // Search needs to be restarted dwNumContChunksFound = 0; i += 32; } // If the whole DWORD is 0x00000000 (meaning none of the // chunks is yet allocated) then we could grab all while ((i < (INT)dwStartLastBlock) && (CHUNKNUM_ELEM((*pAlloc->pMMap), i) == 0x00000000) && !(dwNumContChunksFound >= dwContChunksNeeded)) { if (dwNumContChunksFound == 0) { dwCurrStartChunk = i; } i += 32; dwNumContChunksFound += 32; } if (dwNumContChunksFound >= dwContChunksNeeded) { break; // We've found a suitable place! Break loop here } else if(!(i < (INT)pAlloc->dwMaxChunks)) { break; // finished examining all memory, break loop here } } #endif // ALLOC_OPTIMIZE if (MEM_CHUNK_VAL((*pAlloc->pMMap), i) == 0) { if (dwNumContChunksFound == 0) { // our count so far of contigous chunks is zero, // meaning that were just starting to find free // chunks. We need to remember where this block // is starting dwCurrStartChunk = i; } dwNumContChunksFound++; } else { // This chunk is being used and we haven't found a suitable // set of chunks, so reset our count of contigous chunks // found so far dwNumContChunksFound = 0; } if (dwNumContChunksFound >= dwContChunksNeeded) { // We've found a suitable place! break; // break loop here } } } // If we found a suitable place lets allocate in it if (dwNumContChunksFound >= dwContChunksNeeded) { // Fill in the return pointer (properly aligned) lpMemReq->pMem = __LIN_AlignPtr(CHUNK_NUM_TO_PTR(pAlloc, dwCurrStartChunk), lpMemReq->dwAlign); for (i = dwCurrStartChunk ; i < (INT)(dwCurrStartChunk + dwContChunksNeeded); i++) { // Set up the bits in the memory map to indicate those // addresses are being used. SET_MEM_CHUNK((*pAlloc->pMMap), i); // Clear the bits in the lenght memory map to indicate that // the alloacted block doesn't end here. CLR_MEM_CHUNK((*pAlloc->pLenMap), i); } // Now set the last bit of the lenght map in order to indicate // end-of-allocated-block SET_MEM_CHUNK((*pAlloc->pLenMap), dwCurrStartChunk + dwContChunksNeeded - 1); return GLDD_SUCCESS; } return GLDD_NOMEM; } // _DX_LIN_AllocateLinearMemory //----------------------------------------------------------------------------- // // _DX_LIN_FreeLinearMemory // // This is the interface to memory freeing. // //----------------------------------------------------------------------------- DWORD _DX_LIN_FreeLinearMemory( pLinearAllocatorInfo pAlloc, DWORD VidPointer) { // Reinitialize heap allocater if needed __LIN_ReInitWhenNeeded(pAlloc); if (pAlloc && pAlloc->pMMap && pAlloc->pLenMap) { DWORD i, dwFirstChunk; BOOL bLast = FALSE; // Now compute the starting chunk for this VidMem ptr dwFirstChunk = MEM_PTR_TO_CHUNK_NUM(pAlloc, VidPointer); // Clear the relevant bits in the memory map until the // lenght map indicates we've reached the end of the allocated // block i = dwFirstChunk; while ((!bLast) && (i <= pAlloc->dwMaxChunks)) { // First check if this is the end of the block bLast = MEM_CHUNK_VAL((*pAlloc->pLenMap), i ); // Now "delete" it (even if its the end of the block) CLR_MEM_CHUNK((*pAlloc->pMMap), i); // Set the bits in the lenght memory map for future // allocations. SET_MEM_CHUNK((*pAlloc->pLenMap), i); i++; } return GLDD_SUCCESS; } return GLDD_NOMEM; } // _DX_LIN_FreeLinearMemory //----------------------------------------------------------------------------- // // _DX_LIN_GetFreeMemInHeap // // Scans the memory map and reports the memory that is available in it. // //----------------------------------------------------------------------------- DWORD _DX_LIN_GetFreeMemInHeap( pLinearAllocatorInfo pAlloc) { DWORD dwTotalFreeMem = 0; DWORD dwLargestBlock = 0; DWORD dwTempSize = 0; DWORD i; // Reinitialize heap allocater if needed __LIN_ReInitWhenNeeded(pAlloc); // Make sure the linear allocator & memory map are valid if (pAlloc && pAlloc->pMMap) { for (i = 0; i < pAlloc->dwMaxChunks ; i++) { // Check if chunk is free or in use if (MEM_CHUNK_VAL((*pAlloc->pMMap), i) == 0) { // Keep track of total free memory dwTotalFreeMem++; // Keep track of largest single memory area dwTempSize++; if (dwTempSize > dwLargestBlock) { dwLargestBlock = dwTempSize; } } else { dwTempSize = 0; } } // There is a minimum amount for an allocation to succeed since we have // to pad these/ surfaces out to 32x32, so a 32bpp surface requires at // least 4K free. //@@BEGIN_DDKSPLIT // If we say that we have 1.5K free, then we'll fail TDDRAW WHQL test. Ouch! //@@END_DDKSPLIT if (dwLargestBlock * pAlloc->dwMemPerChunk >= 4096) { return dwTotalFreeMem * pAlloc->dwMemPerChunk; } } return 0; } // _DX_LIN_GetFreeMemInHeap