I implemented my own VkAllocatorCallback using plain C's malloc()/realloc()/free(). It is a naive implementation, and completely ignores the alignment parameter. Taking in account that malloc in 64 bits OS always return pointers with 16 (!) bytes alignment, which is pretty huge alignment, that would not be a problem in my tests. See Reference.
For information completeness, a 16 bytes alignment is also 8/4/2 bytes aligned.
My code is the following:
/**
* PFN_vkAllocationFunction implementation
*/
void* allocationFunction(void* pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope){
printf("pAllocator's allocationFunction: <%s>, size: %u, alignment: %u, allocationScope: %d",
(USER_TYPE)pUserData, size, alignment, allocationScope);
// the allocation itself - ignore alignment, for while
void* ptr = malloc(size);//_aligned_malloc(size, alignment);
memset(ptr, 0, size);
printf(", return ptr* : 0x%p \n", ptr);
return ptr;
}
/**
* The PFN_vkFreeFunction implementation
*/
void freeFunction(void* pUserData, void* pMemory){
printf("pAllocator's freeFunction: <%s> ptr: 0x%p\n",
(USER_TYPE)pUserData, pMemory);
// now, the free operation !
free(pMemory);
}
/**
* The PFN_vkReallocationFunction implementation
*/
void* reallocationFunction(void* pUserData, void* pOriginal, size_t size, size_t alignment, VkSystemAllocationScope allocationScope){
printf("pAllocator's REallocationFunction: <%s>, size %u, alignment %u, allocationScope %d \n",
(USER_TYPE)pUserData, size, alignment, allocationScope);
return realloc(pOriginal, size);
}
/**
* PFN_vkInternalAllocationNotification implementation
*/
void internalAllocationNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope){
printf("pAllocator's internalAllocationNotification: <%s>, size %uz, alignment %uz, allocationType %uz, allocationScope %s \n",
(USER_TYPE)pUserData,
size,
allocationType,
allocationScope);
}
/**
* PFN_vkInternalFreeNotification implementation
**/
void internalFreeNotification(void* pUserData, size_t size, VkInternalAllocationType allocationType, VkSystemAllocationScope allocationScope){
printf("pAllocator's internalFreeNotification: <%s>, size %uz, alignment %uz, allocationType %d, allocationScope %s \n",
(USER_TYPE)pUserData, size, allocationType, allocationScope);
}
/**
* Create Pallocator
* @param info - String for tracking Allocator usage
*/
static VkAllocationCallbacks* createPAllocator(const char* info){
VkAllocationCallbacks* m_allocator = (VkAllocationCallbacks*)malloc(sizeof(VkAllocationCallbacks));
memset(m_allocator, 0, sizeof(VkAllocationCallbacks));
m_allocator->pUserData = (void*)info;
m_allocator->pfnAllocation = (PFN_vkAllocationFunction)(&allocationFunction);
m_allocator->pfnReallocation = (PFN_vkReallocationFunction)(&reallocationFunction);
m_allocator->pfnFree = (PFN_vkFreeFunction)&freeFunction;
m_allocator->pfnInternalAllocation = (PFN_vkInternalAllocationNotification)&internalAllocationNotification;
m_allocator->pfnInternalFree = (PFN_vkInternalFreeNotification)&internalFreeNotification;
// storePAllocator(m_allocator);
return m_allocator;
}
`
I used the Cube.c example, from VulkanSDK, to test my code and assumptions. Modified versions is available here GitHub
A sample of output:
pAllocator's allocationFunction: <Device>, size: 800, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061ECE40
pAllocator's allocationFunction: <RenderPass>, size: 128, alignment: 8, allocationScope: 1, return ptr* : 0x000000000623FAB0
pAllocator's allocationFunction: <ShaderModule>, size: 96, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F2C30
pAllocator's allocationFunction: <ShaderModule>, size: 96, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F8790
pAllocator's allocationFunction: <PipelineCache>, size: 152, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F2590
pAllocator's allocationFunction: <Device>, size: 424, alignment: 8, allocationScope: 1, return ptr* : 0x00000000061F8EB0
pAllocator's freeFunction: <ShaderModule> ptr: 0x00000000061F8790
pAllocator's freeFunction: <ShaderModule> ptr: 0x00000000061F2C30
pAllocator's allocationFunction: <Device>, size: 3448, alignment: 8, allocationScope: 1, return ptr* : 0x000000000624D260
pAllocator's allocationFunction: <Device>, size: 3448, alignment: 8, allocationScope: 1, return ptr* : 0x0000000006249A80
Conclusions:
The user implemented PFN_vkAllocationFunction, PFN_vkReallocationFunction,PFN_vkFreeFunction really does malloc/realoc/free operations in behalf of Vulkan. Not sure if they performs ALL allocations, as Vulkan may choose alloc/free some portions by itself.
The output provided by my implementations shows that typical alignment requested is 8 bytes, in my Win 7-64/NVidia. This shows that there is room for optimization, like as kind managed memory, where you grab a large chunk of memory and sub-allocate for your Vulkan app (a memory pool). It may* reduces memory usage (think 8 bytes before and up to 8 bytes after each alloc'ed block). It also may be faster, as malloc() call can last longer than a direct pointer to your own pool of memory already alloc'ed.
At least with my current Vulkan drivers, the PFN_vkInternalAllocationNotification and PFN_vkInternalFreeNotification doesn't run. Perhaps a bug in my NVidia drivers. I'll check in my AMD later.
The *pUserData is to be used to both debug info and/or management. Actually, you can used it to pass a C++ object, and play all required performance job over there. It's a sort of obvious info, but you can change it for each call or VkCreateXXX object.
You can use a single and generic VkAllocatorCallBack allocator for all application, but I guess that using a customised allocator may lead to better results. I my test, VkSemaphore creation shows a typical pattern on intense alloc/free of small chunks (72 bytes), which may be addressed with reuse of a previously chunk on memory, in a customised allocator. malloc()/free() already reuse memory when possible, but is tempting try to use our own memory manager, at least for short lived small blocks of memory.
Memory alignment maybe an issue to implement VkAllocationCallback (there is no _aligned_realoc function available, but only _aligned_malloc and _aligned_free). But only if Vulkan requests alignments bigger than malloc's default (8 bytes for x86, 16 for AMD64, etc. must check ARM defaults). But so far, seens Vulkan actually request memory with lower alignment than malloc() defaults, at least on 64bit OS's.
Final Thought:
You can live happy until the end of time just setting all VkAllocatorCallback* pAllocator you find as NULL ;)
Possibly Vulkan's default allocator already does it all better than yourself.
BUT...
One of highlights of Vulkan benefits was the developer would be put in control of everything, including memory-management. Khronos presentation, slide 6