gtk/gsk/gpu/gskvulkanmemory.c
Benjamin Otte 057479c284 gpu: Improve memory handling on Vulkan
We now handle the case where memory is not HOST_CACHED.

We also track the memory type now so we can avoid mapping image memory
that is not HOST_CACHED and use buffer transfers instead.
2024-01-07 07:22:52 +01:00

440 lines
13 KiB
C

#include "config.h"
#include "gskvulkanmemoryprivate.h"
/* for GSK_VK_CHECK */
#include "gskvulkandeviceprivate.h"
/* {{{ direct allocator ***/
typedef struct _GskVulkanDirectAllocator GskVulkanDirectAllocator;
struct _GskVulkanDirectAllocator
{
GskVulkanAllocator allocator_class;
VkDevice device; /* no reference held */
uint32_t vk_memory_type_index;
VkMemoryType vk_memory_type;
};
static void
gsk_vulkan_direct_allocator_free_allocator (GskVulkanAllocator *allocator)
{
g_free (allocator);
}
static void
gsk_vulkan_direct_allocator_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize alignment,
GskVulkanAllocation *alloc)
{
GskVulkanDirectAllocator *self = (GskVulkanDirectAllocator *) allocator;
GSK_VK_CHECK (vkAllocateMemory, self->device,
&(VkMemoryAllocateInfo) {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = size,
.memoryTypeIndex = self->vk_memory_type_index
},
NULL,
&alloc->vk_memory);
if ((self->vk_memory_type.propertyFlags & GSK_VULKAN_MEMORY_MAPPABLE) == GSK_VULKAN_MEMORY_MAPPABLE)
{
GSK_VK_CHECK (vkMapMemory, self->device,
alloc->vk_memory,
0,
size,
0,
(void **) &alloc->map);
}
else
{
alloc->map = NULL;
}
alloc->offset = 0;
alloc->size = size;
alloc->memory_flags = self->vk_memory_type.propertyFlags;
}
static void
gsk_vulkan_direct_allocator_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
GskVulkanDirectAllocator *self = (GskVulkanDirectAllocator *) allocator;
if (alloc->map)
vkUnmapMemory (self->device, alloc->vk_memory);
vkFreeMemory (self->device,
alloc->vk_memory,
NULL);
}
GskVulkanAllocator *
gsk_vulkan_direct_allocator_new (VkDevice device,
uint32_t vk_type_index,
const VkMemoryType *vk_type)
{
GskVulkanDirectAllocator *self;
self = g_new0 (GskVulkanDirectAllocator, 1);
self->allocator_class.ref_count = 1;
self->allocator_class.free_allocator = gsk_vulkan_direct_allocator_free_allocator;
self->allocator_class.alloc = gsk_vulkan_direct_allocator_alloc;
self->allocator_class.free = gsk_vulkan_direct_allocator_free;
self->device = device;
self->vk_memory_type_index = vk_type_index;
self->vk_memory_type = *vk_type;
return (GskVulkanAllocator *) self;
}
/* }}} */
/* {{{ buddy allocator ***/
#define GDK_ARRAY_NAME gsk_vulkan_allocation_list
#define GDK_ARRAY_TYPE_NAME GskVulkanAllocationList
#define GDK_ARRAY_ELEMENT_TYPE GskVulkanAllocation
#define GDK_ARRAY_BY_VALUE 1
#define GDK_ARRAY_PREALLOC 4
#define GDK_ARRAY_NO_MEMSET 1
#include "gdk/gdkarrayimpl.c"
#define N_SUBDIVISIONS 10
typedef struct _GskVulkanBuddyAllocator GskVulkanBuddyAllocator;
struct _GskVulkanBuddyAllocator
{
GskVulkanAllocator allocator_class;
GskVulkanAllocator *allocator;
gsize block_size_slot;
GskVulkanAllocation cache;
GskVulkanAllocationList free_lists[N_SUBDIVISIONS];
};
static void
gsk_vulkan_buddy_allocator_free_allocator (GskVulkanAllocator *allocator)
{
GskVulkanBuddyAllocator *self = (GskVulkanBuddyAllocator *) allocator;
gsize i;
if (self->cache.vk_memory)
gsk_vulkan_free (self->allocator, &self->cache);
for (i = 0; i < N_SUBDIVISIONS; i++)
{
gsk_vulkan_allocation_list_clear (&self->free_lists[i]);
}
gsk_vulkan_allocator_unref (self->allocator);
g_free (self);
}
/* must not be 0:
* gets exponent for next power of 2 that's >= num.
* So num=1234 gets 11, because 2048 = 2^11 */
static gsize
find_slot (gsize num)
{
return g_bit_storage (num - 1);
}
static void
gsk_vulkan_buddy_allocator_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize align,
GskVulkanAllocation *alloc)
{
GskVulkanBuddyAllocator *self = (GskVulkanBuddyAllocator *) allocator;
gsize slot;
int i;
size = MAX (size, align);
slot = find_slot (size);
if (slot >= self->block_size_slot)
{
gsk_vulkan_alloc (self->allocator, size, align, alloc);
return;
}
slot = MIN (self->block_size_slot - slot, N_SUBDIVISIONS) - 1;
for (i = slot; i >= 0; i--)
{
if (gsk_vulkan_allocation_list_get_size (&self->free_lists[i]) > 0)
break;
}
if (i < 0)
{
if (self->cache.vk_memory)
{
*alloc = self->cache;
self->cache.vk_memory = NULL;
}
else
{
/* We force alignment to our size, so that we can use offset
* to find the buddy allocation.
*/
gsk_vulkan_alloc (self->allocator, 1 << self->block_size_slot, 1 << self->block_size_slot, alloc);
}
}
else
{
gsize n = gsk_vulkan_allocation_list_get_size (&self->free_lists[i]);
*alloc = *gsk_vulkan_allocation_list_get (&self->free_lists[i], n - 1);
gsk_vulkan_allocation_list_set_size (&self->free_lists[i], n - 1);
}
while (i != slot)
{
i++;
alloc->size >>= 1;
gsk_vulkan_allocation_list_append (&self->free_lists[i], alloc);
alloc->offset += alloc->size;
if (alloc->map)
alloc->map += alloc->size;
}
g_assert (alloc->size >= size);
}
static void
gsk_vulkan_buddy_allocator_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
GskVulkanBuddyAllocator *self = (GskVulkanBuddyAllocator *) allocator;
gsize slot, i, n;
slot = find_slot (alloc->size);
if (slot >= self->block_size_slot)
{
gsk_vulkan_free (self->allocator, alloc);
return;
}
slot = MIN (self->block_size_slot - slot, N_SUBDIVISIONS) - 1;
restart:
n = gsk_vulkan_allocation_list_get_size (&self->free_lists[slot]);
for (i = 0; i < n; i++)
{
GskVulkanAllocation *maybe_buddy = gsk_vulkan_allocation_list_index (&self->free_lists[slot], i);
if (maybe_buddy->vk_memory == alloc->vk_memory &&
maybe_buddy->offset == (alloc->offset ^ alloc->size))
{
if (i < n - 1)
*maybe_buddy = *gsk_vulkan_allocation_list_get (&self->free_lists[slot], n - 1);
gsk_vulkan_allocation_list_set_size (&self->free_lists[slot], n - 1);
if (alloc->map && alloc->offset & alloc->size)
alloc->map -= alloc->size;
alloc->offset &= ~alloc->size;
alloc->size <<= 1;
if (slot == 0)
{
if (self->cache.vk_memory == NULL)
self->cache = *alloc;
else
gsk_vulkan_free (self->allocator, alloc);
return;
}
else
{
slot--;
/* no idea how to make this look good with loops */
goto restart;
}
}
}
gsk_vulkan_allocation_list_append (&self->free_lists[slot], alloc);
}
GskVulkanAllocator *
gsk_vulkan_buddy_allocator_new (GskVulkanAllocator *allocator,
gsize block_size)
{
GskVulkanBuddyAllocator *self;
gsize i;
self = g_new0 (GskVulkanBuddyAllocator, 1);
self->allocator_class.ref_count = 1;
self->allocator_class.free_allocator = gsk_vulkan_buddy_allocator_free_allocator;
self->allocator_class.alloc = gsk_vulkan_buddy_allocator_alloc;
self->allocator_class.free = gsk_vulkan_buddy_allocator_free;
self->allocator = allocator;
self->block_size_slot = find_slot (block_size);
for (i = 0; i < N_SUBDIVISIONS; i++)
{
gsk_vulkan_allocation_list_init (&self->free_lists[i]);
}
return (GskVulkanAllocator *) self;
}
/* }}} */
/* {{{ stats allocator ***/
typedef struct _GskVulkanStatsAllocator GskVulkanStatsAllocator;
struct _GskVulkanStatsAllocator
{
GskVulkanAllocator allocator_class;
GskVulkanAllocator *allocator;
gsize n_alloc;
gsize n_free;
gsize n_bytes_requested;
gsize n_bytes_allocated;
gsize n_bytes_freed;
};
static void
gsk_vulkan_stats_allocator_dump_stats (GskVulkanStatsAllocator *self,
const char *reason)
{
g_printerr ("%s\n", reason);
g_printerr (" %zu bytes requested in %zu allocations\n", self->n_bytes_requested, self->n_alloc);
g_printerr (" %zu bytes allocated (%.2f%% overhead)\n", self->n_bytes_allocated,
(self->n_bytes_allocated - self->n_bytes_requested) * 100. / self->n_bytes_requested);
g_printerr (" %zu bytes freed in %zu frees\n", self->n_bytes_freed , self->n_free);
g_printerr (" %zu bytes remaining in %zu allocations\n",
self->n_bytes_allocated - self->n_bytes_freed, self->n_alloc - self->n_free);
}
static void
gsk_vulkan_stats_allocator_free_allocator (GskVulkanAllocator *allocator)
{
GskVulkanStatsAllocator *self = (GskVulkanStatsAllocator *) allocator;
g_assert (self->n_alloc == self->n_free);
g_assert (self->n_bytes_allocated == self->n_bytes_freed);
gsk_vulkan_allocator_unref (self->allocator);
g_free (self);
}
static void
gsk_vulkan_stats_allocator_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize align,
GskVulkanAllocation *alloc)
{
GskVulkanStatsAllocator *self = (GskVulkanStatsAllocator *) allocator;
gsk_vulkan_alloc (self->allocator, size, align, alloc);
self->n_alloc++;
self->n_bytes_requested += size;
self->n_bytes_allocated += alloc->size;
gsk_vulkan_stats_allocator_dump_stats (self, "alloc()");
}
static void
gsk_vulkan_stats_allocator_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
GskVulkanStatsAllocator *self = (GskVulkanStatsAllocator *) allocator;
self->n_free++;
self->n_bytes_freed += alloc->size;
gsk_vulkan_free (self->allocator, alloc);
gsk_vulkan_stats_allocator_dump_stats (self, "free()");
}
GskVulkanAllocator *
gsk_vulkan_stats_allocator_new (GskVulkanAllocator *allocator)
{
GskVulkanStatsAllocator *self;
self = g_new0 (GskVulkanStatsAllocator, 1);
self->allocator_class.ref_count = 1;
self->allocator_class.free_allocator = gsk_vulkan_stats_allocator_free_allocator;
self->allocator_class.alloc = gsk_vulkan_stats_allocator_alloc;
self->allocator_class.free = gsk_vulkan_stats_allocator_free;
self->allocator = allocator;
return (GskVulkanAllocator *) self;
}
/* }}} */
/* {{{ external allocator ***/
typedef struct _GskVulkanExteralAllocator GskVulkanExteralAllocator;
struct _GskVulkanExteralAllocator
{
GskVulkanAllocator allocator_class;
VkDevice device; /* no reference held */
};
static void
gsk_vulkan_external_allocator_free_allocator (GskVulkanAllocator *allocator)
{
g_free (allocator);
}
static void
gsk_vulkan_external_allocator_alloc (GskVulkanAllocator *allocator,
VkDeviceSize size,
VkDeviceSize alignment,
GskVulkanAllocation *alloc)
{
alloc->vk_memory = VK_NULL_HANDLE;
alloc->map = NULL;
alloc->offset = 0;
alloc->size = size;
alloc->memory_flags = 0;
}
static void
gsk_vulkan_external_allocator_free (GskVulkanAllocator *allocator,
GskVulkanAllocation *alloc)
{
GskVulkanExteralAllocator *self = (GskVulkanExteralAllocator *) allocator;
g_assert (alloc->map == NULL);
if (alloc->vk_memory)
vkFreeMemory (self->device,
alloc->vk_memory,
NULL);
}
/* The external allocator assumes you call alloc() and then set alloc->vk_memory
* manually.
* You can even usnet it before calling free() if you set it back to VK_NULL_HANDLE.
*/
GskVulkanAllocator *
gsk_vulkan_external_allocator_new (VkDevice device)
{
GskVulkanExteralAllocator *self;
self = g_new0 (GskVulkanExteralAllocator, 1);
self->allocator_class.ref_count = 1;
self->allocator_class.free_allocator = gsk_vulkan_external_allocator_free_allocator;
self->allocator_class.alloc = gsk_vulkan_external_allocator_alloc;
self->allocator_class.free = gsk_vulkan_external_allocator_free;
self->device = device;
return (GskVulkanAllocator *) self;
}
/* }}} */