summaryrefslogtreecommitdiff
path: root/rend_vk_arena.c
diff options
context:
space:
mode:
authorVasco <[email protected]>2026-08-12 17:47:50 +0100
committerVasco <[email protected]>2026-08-12 17:47:50 +0100
commit95e3e76fd9c789cbf2d4409ef49456addc71643f (patch)
tree34161614f5f0ae909f58eb5e58a2ffd26c2f0e9f /rend_vk_arena.c
rend 1.0.1HEADmain
Diffstat (limited to 'rend_vk_arena.c')
-rw-r--r--rend_vk_arena.c264
1 files changed, 264 insertions, 0 deletions
diff --git a/rend_vk_arena.c b/rend_vk_arena.c
new file mode 100644
index 0000000..b936be4
--- /dev/null
+++ b/rend_vk_arena.c
@@ -0,0 +1,264 @@
+#pragma once
+#include "rend_internal.h"
+#include "rend_vk_internal.h"
+#include <vulkan/vulkan_core.h>
+
+/* arenas need to be subdivided into blocks
+ * just like pools because we must respect page size */
+typedef struct RendVkPage {
+ RendMemory memory;
+ size_t head;
+ int reserved;
+} RendVkPage;
+
+typedef struct RendVkPagedArena {
+ RendVkPage *page_darr;
+ uint32_t capacity;
+ uint32_t elements;
+} RendVkPagedArena;
+
+struct RendVkArenaAllocator {
+ VkAllocationCallbacks *allocator;
+ size_t *heap_idx_alloc_sizes; // allocation size per type of memory available on the gpu
+ RendVkPagedArena *mem_arenas; // memory arena per type of memory available on the gpu
+ VkDevice logical_device; // virtual device
+ VkPhysicalDevice physical_device; // physical device we are allocating memory from
+ VkDeviceSize gpu_alignment; // allocations must respect the physical limitations of the gpu
+ VkDeviceSize block_min_size; // minimum size per block of memory
+ uint64_t total_allocations;
+ uint32_t heap_index_count;
+ VkPhysicalDeviceMemoryProperties properties; // useful for getting properties per heap index
+};
+
+static RendVkArenaAllocator
+rend_vk_arena_create(VkDevice logical_device, VkPhysicalDevice physical_device, VkPhysicalDeviceLimits device_limits, VkAllocationCallbacks *allocator)
+{
+ RendVkArenaAllocator arena = {
+ .allocator = allocator,
+ .heap_idx_alloc_sizes = NULL,
+ .logical_device = logical_device,
+ .physical_device = physical_device,
+ .gpu_alignment = 0,
+ .block_min_size = 0,
+ .total_allocations = 0,
+ .heap_index_count = 0,
+ .mem_arenas = 0,
+ };
+
+ VkPhysicalDeviceMemoryProperties mem_properties;
+ vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties);
+ arena.properties = mem_properties;
+
+ uint32_t count = mem_properties.memoryTypeCount;
+ arena.heap_index_count = count;
+
+ arena.heap_idx_alloc_sizes = rmalloc(count * sizeof *arena.heap_idx_alloc_sizes);
+ memset(arena.heap_idx_alloc_sizes, 0, count * sizeof *arena.heap_idx_alloc_sizes);
+
+ arena.mem_arenas = rmalloc(count * sizeof *arena.mem_arenas);
+ for (uint32_t u = 0; u < count; ++u) {
+ arena.mem_arenas[u].capacity = 2;
+ arena.mem_arenas[u].elements = 0;
+ arena.mem_arenas[u].page_darr = rmalloc(2 * sizeof *arena.mem_arenas[u].page_darr);
+ memset(arena.mem_arenas[u].page_darr, 0, 2 * sizeof *arena.mem_arenas[u].page_darr);
+ }
+
+ VkDeviceSize alignment = device_limits.bufferImageGranularity;
+ if (device_limits.nonCoherentAtomSize > alignment) {
+ alignment = device_limits.nonCoherentAtomSize;
+ }
+
+ arena.gpu_alignment = alignment;
+ arena.block_min_size = arena.gpu_alignment * 10;
+
+ return arena;
+}
+
+static uint32_t
+rend_vk_arena_add_page(RendVkArenaAllocator *arena, VkDeviceSize size, uint32_t heap_index, bool fit_to_alloc)
+{
+ VkDeviceSize new_arena_size = fit_to_alloc ? size : (size * 2);
+ new_arena_size = (new_arena_size < arena->block_min_size) ? arena->block_min_size : new_arena_size;
+
+ VkMemoryPropertyFlags properties = arena->properties.memoryTypes[heap_index].propertyFlags;
+
+ RendVkPage page = (RendVkPage) {0};
+ page.head = 0;
+
+ VkMemoryAllocateFlagsInfo flags_info = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
+ .pNext = NULL,
+ .flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT,
+ .deviceMask = 0
+ };
+
+ VkMemoryAllocateInfo alloc_info = {
+ .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
+ .allocationSize = new_arena_size,
+ .pNext = &flags_info,
+ .memoryTypeIndex = heap_index
+ };
+
+ page.memory = (RendMemory) {0};
+ page.memory.offset = 0;
+ page.memory.size = new_arena_size;
+
+ VkResult res = vkAllocateMemory(arena->logical_device, &alloc_info, arena->allocator, (VkDeviceMemory*) &page.memory.device_memory);
+ if (res != VK_SUCCESS) {
+ return UINT32_MAX;
+ }
+
+ if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
+ vkMapMemory(
+ arena->logical_device,
+ (VkDeviceMemory) page.memory.device_memory,
+ 0, // Offset inside VkDeviceMemory!!!1
+ new_arena_size, // MUST match alloc_info.allocationSize and NOT raw size!!!
+ 0,
+ &page.memory.host_mapped_memory
+ );
+ }
+
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[heap_index];
+
+ if (mem_arena->elements + 1 >= mem_arena->capacity) {
+ uint32_t new_capacity = mem_arena->capacity * 2;
+ void *new_darr = rrealloc(mem_arena->page_darr, new_capacity * sizeof *mem_arena->page_darr);
+ if (!new_darr) {
+ vkFreeMemory(arena->logical_device, (VkDeviceMemory) page.memory.device_memory, arena->allocator);
+ return UINT32_MAX;
+ }
+ mem_arena->page_darr = new_darr;
+ mem_arena->capacity = new_capacity;
+ }
+
+ uint32_t page_index = mem_arena->elements;
+ mem_arena->page_darr[mem_arena->elements++] = page;
+
+ arena->total_allocations++;
+
+ return page_index;
+}
+
+static RendMemory
+rend_vk_arena_alloc(RendVkArenaAllocator *arena, VkDeviceSize size, uint32_t heap_index)
+{
+ assert(heap_index < 32 && "Unusual heap index. Did you pass the memory type instead?");
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[heap_index];
+
+ VkMemoryPropertyFlags properties = arena->properties.memoryTypes[heap_index].propertyFlags;
+
+ /* align to allocation to fit page size */
+ VkDeviceSize align = arena->gpu_alignment;
+ VkDeviceSize aligned_size = (size + align - 1) & ~(align - 1);
+
+ /* check if fits in previous blocks */
+ int whole_page = !(properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ uint32_t page_idx = UINT32_MAX;
+ for (uint32_t u = 0; u < mem_arena->elements; ++u) {
+ RendVkPage page = mem_arena->page_darr[u];
+ int valid = (whole_page) ? (page.head == 0) : 1;
+ if ((page.head + aligned_size <= page.memory.size) && valid) {
+ page_idx = u;
+ break;
+ }
+ }
+
+ /* page not found, add new page */
+ if (page_idx == UINT32_MAX) {
+ page_idx = rend_vk_arena_add_page(arena, aligned_size, heap_index, whole_page);
+ if (page_idx == UINT32_MAX) {
+ REND__CRASH("[REND_VK] Arena page allocation failed!");
+ }
+ }
+
+ mem_arena->page_darr[page_idx].reserved = whole_page;
+
+ RendMemory memory = {
+ .device_memory = mem_arena->page_darr[page_idx].memory.device_memory,
+ .size = aligned_size,
+ .offset = mem_arena->page_darr[page_idx].head,
+ .host_mapped_memory = 0,
+ .heap_index = heap_index,
+ .id = page_idx,
+ };
+
+ if (properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) {
+ memory.host_mapped_memory = mem_arena->page_darr[page_idx].memory.host_mapped_memory + memory.offset;
+ }
+
+ /* move head */
+ arena->heap_idx_alloc_sizes[heap_index] += aligned_size;
+ mem_arena->page_darr[page_idx].head += aligned_size;
+ return memory;
+}
+
+
+static void
+rend_vk_arena_clear(RendVkArenaAllocator *arena, uint32_t heap_index)
+{
+ assert(heap_index < 32 && "Unusual heap index. Did you pass the memory type instead?");
+ RendVkPagedArena mem_arena = arena->mem_arenas[heap_index];
+ if (mem_arena.page_darr) {
+ for (uint32_t u = 0; u < mem_arena.elements; ++u) {
+ mem_arena.page_darr[u].head = 0;
+ mem_arena.page_darr[u].reserved = 0;
+ }
+ }
+}
+
+static void
+rend_vk_arena_clear_all(RendVkArenaAllocator *arena)
+{
+ for (uint32_t u = 0; u < arena->properties.memoryTypeCount; ++u) {
+ rend_vk_arena_clear(arena, u);
+ }
+}
+
+static void
+rend_vk_arena_shrink(RendVkArenaAllocator *arena, uint32_t heap_index)
+{
+ assert(heap_index < 32 && "Unusual heap index. Did you pass the memory type instead?");
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[heap_index];
+ if (mem_arena->capacity > 4) {
+ uint32_t new_capacity = mem_arena->capacity / 2;
+ void *new_darr = rrealloc(mem_arena->page_darr, new_capacity * sizeof *mem_arena->page_darr);
+ if (!new_darr) return;
+ mem_arena->page_darr = new_darr;
+ mem_arena->capacity = new_capacity;
+ }
+}
+
+static void
+rend_vk_arena_destroy(RendVkArenaAllocator *arena)
+{
+ if (arena->mem_arenas) {
+ for (size_t u = 0; u < arena->heap_index_count; ++u) {
+ RendVkPagedArena *mem_arena = &arena->mem_arenas[u];
+ for (uint32_t p = 0; p < mem_arena->elements; ++p) {
+
+ RendMemory memory = mem_arena->page_darr[p].memory ;
+ if (memory.offset == 0) {
+ if (memory.host_mapped_memory) {
+ vkUnmapMemory(arena->logical_device, (VkDeviceMemory) memory.device_memory);
+ }
+ vkFreeMemory(arena->logical_device, (VkDeviceMemory) memory.device_memory, arena->allocator);
+ } else {
+ PWARN("[REND_VK] Attempted to free memory with an offset!");
+ }
+
+ }
+ if (mem_arena->page_darr) {
+ rfree(mem_arena->page_darr);
+ mem_arena->page_darr = 0;
+ }
+ }
+ rfree(arena->mem_arenas);
+ arena->mem_arenas = 0;
+ }
+ if (arena->heap_idx_alloc_sizes) {
+ rfree(arena->heap_idx_alloc_sizes);
+ arena->heap_idx_alloc_sizes = 0;
+ }
+ memset(arena, 0, sizeof *arena);
+}