Image memory allocator.

Started process of removing demo project's allocator code.
This commit is contained in:
Artem Kharytoniuk 2017-04-24 10:35:19 +03:00
parent f823930d83
commit 6dab078992
4 changed files with 142 additions and 127 deletions

View File

@ -40,6 +40,20 @@ static bool is_extension_available(const std::vector<VkExtensionProperties>& pro
return false;
}
static uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t memory_type_bits, VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memory_properties;
vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) {
if ((memory_type_bits & (1 << i)) != 0 &&
(memory_properties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
ri.Error(ERR_FATAL, "Vulkan error: failed to find matching memory type with requested properties");
return -1;
}
static VkFormat find_format_with_features(VkPhysicalDevice physical_device, const std::vector<VkFormat>& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) {
for (VkFormat format : candidates) {
VkFormatProperties properties;
@ -431,6 +445,61 @@ static void record_image_layout_transition(VkCommandBuffer command_buffer, VkIma
0, nullptr, 0, nullptr, 1, &barrier);
}
static void allocate_and_bind_image_memory(VkImage image) {
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(vk.device, image, &memory_requirements);
if (memory_requirements.size > IMAGE_CHUNK_SIZE) {
ri.Error(ERR_FATAL, "Vulkan: could not allocate memory, image is too large.");
}
Vulkan_Resources::Chunk* chunk = nullptr;
// Try to find an existing chunk of sufficient capacity.
const auto mask = ~(memory_requirements.alignment - 1);
for (int i = 0; i < tr.vk_resources.num_image_chunks; i++) {
// ensure that memory region has proper alignment
VkDeviceSize offset = (tr.vk_resources.image_chunks[i].used + memory_requirements.alignment - 1) & mask;
if (offset + memory_requirements.size <= IMAGE_CHUNK_SIZE) {
chunk = &tr.vk_resources.image_chunks[i];
chunk->used = offset + memory_requirements.size;
break;
}
}
// Allocate a new chunk in case we couldn't find suitable existing chunk.
if (chunk == nullptr) {
if (tr.vk_resources.num_image_chunks >= MAX_IMAGE_CHUNKS) {
ri.Error(ERR_FATAL, "Vulkan: image chunk limit has been reached");
}
VkMemoryAllocateInfo alloc_info;
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = nullptr;
alloc_info.allocationSize = IMAGE_CHUNK_SIZE;
alloc_info.memoryTypeIndex = find_memory_type(vk.physical_device, memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VkDeviceMemory memory;
VK_CHECK(vkAllocateMemory(vk.device, &alloc_info, nullptr, &memory));
chunk = &tr.vk_resources.image_chunks[tr.vk_resources.num_image_chunks];
tr.vk_resources.num_image_chunks++;
chunk->memory = memory;
chunk->used = memory_requirements.size;
}
VK_CHECK(vkBindImageMemory(vk.device, image, chunk->memory, chunk->used - memory_requirements.size));
}
static void deallocate_image_chunks() {
for (int i = 0; i < tr.vk_resources.num_image_chunks; i++) {
vkFreeMemory(vk.device, tr.vk_resources.image_chunks[i].memory, nullptr);
}
tr.vk_resources.num_image_chunks = 0;
Com_Memset(tr.vk_resources.image_chunks, 0, sizeof(tr.vk_resources.image_chunks));
}
VkPipeline create_pipeline(const Vk_Pipeline_Desc&);
bool vk_initialize(HWND hwnd) {
@ -497,8 +566,6 @@ bool vk_initialize(HWND hwnd) {
VK_CHECK(vkAllocateCommandBuffers(vk.device, &alloc_info, &g.command_buffer));
}
get_allocator()->initialize(vk.physical_device, vk.device);
//
// Depth attachment image.
//
@ -526,9 +593,17 @@ bool vk_initialize(HWND hwnd) {
VK_CHECK(vkCreateImage(vk.device, &desc, nullptr, &vk.depth_image));
VkDeviceMemory memory = get_allocator()->allocate_memory(vk.depth_image);
VK_CHECK(vkBindImageMemory(vk.device, vk.depth_image, memory, 0));
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(vk.device, vk.depth_image, &memory_requirements);
VkMemoryAllocateInfo alloc_info;
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = nullptr;
alloc_info.allocationSize = memory_requirements.size;
alloc_info.memoryTypeIndex = find_memory_type(vk.physical_device, memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK(vkAllocateMemory(vk.device, &alloc_info, nullptr, &vk.depth_image_memory));
VK_CHECK(vkBindImageMemory(vk.device, vk.depth_image, vk.depth_image_memory, 0));
vk.depth_image_view = create_image_view(vk.depth_image, depth_format, VK_IMAGE_ASPECT_DEPTH_BIT);
record_and_run_commands(vk.command_pool, vk.queue, [&depth_format](VkCommandBuffer command_buffer) {
@ -765,8 +840,10 @@ void vk_deinitialize() {
vk_log_file = nullptr;
get_allocator()->deallocate_all();
deallocate_image_chunks();
vkDestroyImage(vk.device, vk.depth_image, nullptr);
vkFreeMemory(vk.device, vk.depth_image_memory, nullptr);
vkDestroyImageView(vk.device, vk.depth_image_view, nullptr);
for (uint32_t i = 0; i < vk.swapchain_image_count; i++) {
@ -842,8 +919,8 @@ VkImage vk_create_texture(const uint8_t* rgba_pixels, int image_width, int image
VkBuffer staging_buffer;
VK_CHECK(vkCreateBuffer(vk.device, &buffer_desc, nullptr, &staging_buffer));
get_allocator()->get_shared_staging_memory().ensure_allocation_for_object(staging_buffer);
VkDeviceMemory buffer_memory = get_allocator()->get_shared_staging_memory().get_handle();
get_allocator()->ensure_allocation_for_staging_buffer(staging_buffer);
VkDeviceMemory buffer_memory = get_allocator()->get_staging_buffer_memory();
VK_CHECK(vkBindBufferMemory(vk.device, staging_buffer, buffer_memory, 0));
void* buffer_data;
@ -873,9 +950,7 @@ VkImage vk_create_texture(const uint8_t* rgba_pixels, int image_width, int image
VkImage texture_image;
VK_CHECK(vkCreateImage(vk.device, &desc, nullptr, &texture_image));
VkDeviceMemory memory = get_allocator()->allocate_memory(texture_image);
VK_CHECK(vkBindImageMemory(vk.device, texture_image, memory, 0));
allocate_and_bind_image_memory(texture_image);
// copy buffer's content to texture
record_and_run_commands(vk.command_pool, vk.queue,
@ -950,8 +1025,7 @@ VkImage vk_create_cinematic_image(int width, int height, Vk_Staging_Buffer& stag
VkImage image;
VK_CHECK(vkCreateImage(vk.device, &image_desc, nullptr, &image));
VkDeviceMemory image_memory = get_allocator()->allocate_memory(image);
VK_CHECK(vkBindImageMemory(vk.device, image, image_memory, 0));
allocate_and_bind_image_memory(image);
staging_buffer.handle = buffer;
staging_buffer.memory = buffer_memory;
@ -1374,6 +1448,8 @@ VkDescriptorSet vk_create_descriptor_set(VkImageView image_view) {
void vk_destroy_resources() {
vkDeviceWaitIdle(vk.device);
deallocate_image_chunks();
// Destroy pipelines
for (int i = 0; i < tr.vk_resources.num_pipelines; i++) {
vkDestroyPipeline(vk.device, tr.vk_resources.pipelines[i], nullptr);

View File

@ -11,6 +11,9 @@ const int MAX_SWAPCHAIN_IMAGES = 8;
const int MAX_VK_PIPELINES = 1024;
const int MAX_VK_IMAGES = 2048; // should be the same as MAX_DRAWIMAGES
const int IMAGE_CHUNK_SIZE = 32 * 1024 * 1024;
const int MAX_IMAGE_CHUNKS = 16;
#define VK_CHECK(function_call) { \
VkResult result = function_call; \
if (result < 0) \
@ -113,6 +116,7 @@ struct Vulkan_Instance {
VkCommandBuffer command_buffer = VK_NULL_HANDLE;
VkImage depth_image = VK_NULL_HANDLE;
VkDeviceMemory depth_image_memory = VK_NULL_HANDLE;
VkImageView depth_image_view = VK_NULL_HANDLE;
VkRenderPass render_pass = VK_NULL_HANDLE;
@ -155,4 +159,12 @@ struct Vulkan_Resources {
VkPipeline pipelines[MAX_VK_PIPELINES];
Vk_Image images[MAX_VK_IMAGES];
struct Chunk {
VkDeviceMemory memory = VK_NULL_HANDLE;
VkDeviceSize used = 0;
};
int num_image_chunks = 0;
Chunk image_chunks[MAX_IMAGE_CHUNKS];
};

View File

@ -21,105 +21,59 @@ static uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t memo
return -1;
}
void Shared_Staging_Memory::initialize(VkPhysicalDevice physical_device, VkDevice device) {
this->physical_device = physical_device;
this->device = device;
}
void Shared_Staging_Memory::deallocate_all() {
if (handle != VK_NULL_HANDLE) {
vkFreeMemory(device, handle, nullptr);
}
handle = VK_NULL_HANDLE;
size = 0;
memory_type_index = -1;
}
void Shared_Staging_Memory::ensure_allocation_for_object(VkImage image) {
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(device, image, &memory_requirements);
ensure_allocation(memory_requirements);
}
void Shared_Staging_Memory::ensure_allocation_for_object(VkBuffer buffer) {
VkMemoryRequirements memory_requirements;
vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
ensure_allocation(memory_requirements);
}
VkDeviceMemory Shared_Staging_Memory::get_handle() const {
return handle;
}
void Shared_Staging_Memory::ensure_allocation(const VkMemoryRequirements& memory_requirements) {
uint32_t required_memory_type_index = find_memory_type(physical_device, memory_requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
if (size < memory_requirements.size || memory_type_index != required_memory_type_index) {
if (handle != VK_NULL_HANDLE) {
vkFreeMemory(device, handle, nullptr);
}
handle = VK_NULL_HANDLE;
size = 0;
memory_type_index = -1;
VkMemoryAllocateInfo alloc_info;
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = nullptr;
alloc_info.allocationSize = memory_requirements.size;
alloc_info.memoryTypeIndex = required_memory_type_index;
VK_CHECK(vkAllocateMemory(device, &alloc_info, nullptr, &handle));
size = memory_requirements.size;
memory_type_index = required_memory_type_index;
}
}
void Device_Memory_Allocator::initialize(VkPhysicalDevice physical_device, VkDevice device) {
this->physical_device = physical_device;
this->device = device;
shared_staging_memory.initialize(physical_device, device);
}
void Device_Memory_Allocator::deallocate_all() {
for (auto chunk : chunks) {
vkFreeMemory(device, chunk, nullptr);
vkFreeMemory(vk.device, chunk, nullptr);
}
chunks.clear();
shared_staging_memory.deallocate_all();
}
VkDeviceMemory Device_Memory_Allocator::allocate_memory(VkImage image) {
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(device, image, &memory_requirements);
return allocate_memory(memory_requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
}
VkDeviceMemory Device_Memory_Allocator::allocate_memory(VkBuffer buffer) {
VkMemoryRequirements memory_requirements;
vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
return allocate_memory(memory_requirements, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (staging_buffer_memory != VK_NULL_HANDLE) {
vkFreeMemory(vk.device, staging_buffer_memory, nullptr);
staging_buffer_memory = VK_NULL_HANDLE;
staging_buffer_size = 0;
}
}
VkDeviceMemory Device_Memory_Allocator::allocate_staging_memory(VkBuffer buffer) {
VkMemoryRequirements memory_requirements;
vkGetBufferMemoryRequirements(device, buffer, &memory_requirements);
return allocate_memory(memory_requirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
vkGetBufferMemoryRequirements(vk.device, buffer, &memory_requirements);
Shared_Staging_Memory& Device_Memory_Allocator::get_shared_staging_memory() {
return shared_staging_memory;
}
VkDeviceMemory Device_Memory_Allocator::allocate_memory(const VkMemoryRequirements& memory_requirements, VkMemoryPropertyFlags properties) {
VkMemoryAllocateInfo alloc_info;
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = nullptr;
alloc_info.allocationSize = memory_requirements.size;
alloc_info.memoryTypeIndex = find_memory_type(physical_device, memory_requirements.memoryTypeBits, properties);
alloc_info.memoryTypeIndex = find_memory_type(vk.physical_device, memory_requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VkDeviceMemory chunk;
VK_CHECK(vkAllocateMemory(device, &alloc_info, nullptr, &chunk));
VK_CHECK(vkAllocateMemory(vk.device, &alloc_info, nullptr, &chunk));
chunks.push_back(chunk);
return chunk;
}
void Device_Memory_Allocator::ensure_allocation_for_staging_buffer(VkBuffer buffer) {
VkMemoryRequirements memory_requirements;
vkGetBufferMemoryRequirements(vk.device, buffer, &memory_requirements);
if (staging_buffer_size < memory_requirements.size) {
if (staging_buffer_memory != VK_NULL_HANDLE) {
vkFreeMemory(vk.device, staging_buffer_memory, nullptr);
}
staging_buffer_memory = VK_NULL_HANDLE;
staging_buffer_size = 0;
VkMemoryAllocateInfo alloc_info;
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = nullptr;
alloc_info.allocationSize = memory_requirements.size;
alloc_info.memoryTypeIndex = find_memory_type(vk.physical_device, memory_requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VK_CHECK(vkAllocateMemory(vk.device, &alloc_info, nullptr, &staging_buffer_memory));
staging_buffer_size = memory_requirements.size;
}
}
VkDeviceMemory Device_Memory_Allocator::get_staging_buffer_memory() const {
return staging_buffer_memory;
}

View File

@ -3,49 +3,22 @@
#include "vk.h"
#include <vector>
class Shared_Staging_Memory {
public:
void initialize(VkPhysicalDevice physical_device, VkDevice device);
void deallocate_all();
void ensure_allocation_for_object(VkImage image);
void ensure_allocation_for_object(VkBuffer buffer);
VkDeviceMemory get_handle() const;
private:
void ensure_allocation(const VkMemoryRequirements& memory_requirements);
private:
VkPhysicalDevice physical_device = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
VkDeviceMemory handle = VK_NULL_HANDLE;
VkDeviceSize size = 0;
uint32_t memory_type_index = -1;
};
// NOTE: in this implementation I do memory allocation for each allocation request.
// TODO: sub-allocate from larger chunks and return chunk handle plus offset withing corresponding chunk.
class Device_Memory_Allocator {
public:
void initialize(VkPhysicalDevice physical_device, VkDevice device);
void deallocate_all();
VkDeviceMemory allocate_memory(VkImage image);
VkDeviceMemory allocate_memory(VkBuffer buffer);
VkDeviceMemory allocate_staging_memory(VkBuffer buffer);
Shared_Staging_Memory& get_shared_staging_memory();
void ensure_allocation_for_staging_buffer(VkBuffer buffer);
VkDeviceMemory get_staging_buffer_memory() const;
private:
VkDeviceMemory allocate_memory(const VkMemoryRequirements& memory_requirements, VkMemoryPropertyFlags properties);
private:
VkPhysicalDevice physical_device = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
std::vector<VkDeviceMemory> chunks;
Shared_Staging_Memory shared_staging_memory;
VkDeviceMemory staging_buffer_memory;
VkDeviceSize staging_buffer_size = 0;
};
Device_Memory_Allocator* get_allocator();