Revising Vulkan example to address leak

DangitBen 2022-11-09 23:38:25 -06:00
parent c714ee5249
commit 504473bcaf

@ -551,6 +551,25 @@ Add at the top of the Vulkan example main.cpp file after the `check_vk_result()`
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// A struct to manage data related to one image in vulkan
struct MyTextureData
{
VkDescriptorSet DS; // Descriptor set: this is what you'll pass to Image()
int Width;
int Height;
int Channels;
// Need to keep track of these to properly cleanup
VkImageView ImageView;
VkImage Image;
VkDeviceMemory ImageMemory;
VkSampler Sampler;
VkBuffer UploadBuffer;
VkDeviceMemory UploadBufferMemory;
MyTextureData() { memset(this, 0, sizeof(*this)); }
};
// Helper function to find Vulkan memory type bits. See ImGui_ImplVulkan_MemoryType() in imgui_impl_vulkan.cpp
uint32_t findMemoryType(uint32_t type_filter, VkMemoryPropertyFlags properties)
{
@ -568,12 +587,12 @@ uint32_t findMemoryType(uint32_t type_filter, VkMemoryPropertyFlags properties)
return 0xFFFFFFFF; // Unable to find memoryType
}
// Helper function to load an image with common settings and return a VkDescriptorSet as a sort of Vulkan pointer
bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* image_width, int* image_height)
// Helper function to load an image with common settings and return a MyTextureData with a VkDescriptorSet as a sort of Vulkan pointer
bool LoadTextureFromFile(const char* filename, MyTextureData* tex_data)
{
// Specifying 4 channels forces stb to load the image in RGBA which is an easy format for Vulkan
int image_channels = 4;
unsigned char* image_data = stbi_load(filename, image_width, image_height, 0, image_channels);
tex_data->Channels = 4;
unsigned char* image_data = stbi_load(filename, &tex_data->Width, &tex_data->Height, 0, tex_data->Channels);
if (image_data == NULL)
{
@ -581,20 +600,18 @@ bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* ima
}
// Calculate allocation size (in number of bytes)
size_t image_size = (*image_width)*(*image_height)*image_channels;
size_t image_size = tex_data->Width*tex_data->Height*tex_data->Channels;
VkResult err;
// Create the Vulkan image.
VkImage texture_image;
VkDeviceMemory texture_image_memory;
{
VkImageCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
info.imageType = VK_IMAGE_TYPE_2D;
info.format = VK_FORMAT_R8G8B8A8_UNORM;
info.extent.width = *image_width;
info.extent.height = *image_height;
info.extent.width = tex_data->Width;
info.extent.height = tex_data->Height;
info.extent.depth = 1;
info.mipLevels = 1;
info.arrayLayers = 1;
@ -603,37 +620,35 @@ bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* ima
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
err = vkCreateImage(g_Device, &info, g_Allocator, &texture_image);
err = vkCreateImage(g_Device, &info, g_Allocator, &tex_data->Image);
check_vk_result(err);
VkMemoryRequirements req;
vkGetImageMemoryRequirements(g_Device, texture_image, &req);
vkGetImageMemoryRequirements(g_Device, tex_data->Image, &req);
VkMemoryAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = req.size;
alloc_info.memoryTypeIndex = findMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &texture_image_memory);
err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &tex_data->ImageMemory);
check_vk_result(err);
err = vkBindImageMemory(g_Device, texture_image, texture_image_memory, 0);
err = vkBindImageMemory(g_Device, tex_data->Image, tex_data->ImageMemory, 0);
check_vk_result(err);
}
// Create the Image View
VkImageView image_view;
{
VkImageViewCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.image = texture_image;
info.image = tex_data->Image;
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
info.format = VK_FORMAT_R8G8B8A8_UNORM;
info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
info.subresourceRange.levelCount = 1;
info.subresourceRange.layerCount = 1;
err = vkCreateImageView(g_Device, &info, g_Allocator, &image_view);
err = vkCreateImageView(g_Device, &info, g_Allocator, &tex_data->ImageView);
check_vk_result(err);
}
// Create Sampler
VkSampler sampler;
{
VkSamplerCreateInfo sampler_info{};
sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
@ -646,49 +661,47 @@ bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* ima
sampler_info.minLod = -1000;
sampler_info.maxLod = 1000;
sampler_info.maxAnisotropy = 1.0f;
err = vkCreateSampler(g_Device, &sampler_info, g_Allocator, &sampler);
err = vkCreateSampler(g_Device, &sampler_info, g_Allocator, &tex_data->Sampler);
check_vk_result(err);
}
// Create Descriptor Set using ImGUI's implementation
*img_ds = ImGui_ImplVulkan_AddTexture(sampler, image_view, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
tex_data->DS = ImGui_ImplVulkan_AddTexture(tex_data->Sampler, tex_data->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// Create Upload Buffer
VkBuffer upload_buffer;
VkDeviceMemory upload_buffer_memory;
{
VkBufferCreateInfo buffer_info = {};
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_info.size = image_size;
buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
err = vkCreateBuffer(g_Device, &buffer_info, g_Allocator, &upload_buffer);
err = vkCreateBuffer(g_Device, &buffer_info, g_Allocator, &tex_data->UploadBuffer);
check_vk_result(err);
VkMemoryRequirements req;
vkGetBufferMemoryRequirements(g_Device, upload_buffer, &req);
vkGetBufferMemoryRequirements(g_Device, tex_data->UploadBuffer, &req);
VkMemoryAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = req.size;
alloc_info.memoryTypeIndex = findMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &upload_buffer_memory);
err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &tex_data->UploadBufferMemory);
check_vk_result(err);
err = vkBindBufferMemory(g_Device, upload_buffer, upload_buffer_memory, 0);
err = vkBindBufferMemory(g_Device, tex_data->UploadBuffer, tex_data->UploadBufferMemory, 0);
check_vk_result(err);
}
// Upload to Buffer:
{
void* map = NULL;
err = vkMapMemory(g_Device, upload_buffer_memory, 0, image_size, 0, &map);
err = vkMapMemory(g_Device, tex_data->UploadBufferMemory, 0, image_size, 0, &map);
check_vk_result(err);
memcpy(map, image_data, image_size);
VkMappedMemoryRange range[1] = {};
range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range[0].memory = upload_buffer_memory;
range[0].memory = tex_data->UploadBufferMemory;
range[0].size = image_size;
err = vkFlushMappedMemoryRanges(g_Device, 1, range);
check_vk_result(err);
vkUnmapMemory(g_Device, upload_buffer_memory);
vkUnmapMemory(g_Device, tex_data->UploadBufferMemory);
}
// Release image memory using stb
@ -724,7 +737,7 @@ bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* ima
copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
copy_barrier[0].image = texture_image;
copy_barrier[0].image = tex_data->Image;
copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
copy_barrier[0].subresourceRange.levelCount = 1;
copy_barrier[0].subresourceRange.layerCount = 1;
@ -733,10 +746,10 @@ bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* ima
VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = *image_width;
region.imageExtent.height = *image_height;
region.imageExtent.width = tex_data->Width;
region.imageExtent.height = tex_data->Height;
region.imageExtent.depth = 1;
vkCmdCopyBufferToImage(command_buffer, upload_buffer, texture_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
vkCmdCopyBufferToImage(command_buffer, tex_data->UploadBuffer, tex_data->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkImageMemoryBarrier use_barrier[1] = {};
use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
@ -746,7 +759,7 @@ bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* ima
use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
use_barrier[0].image = texture_image;
use_barrier[0].image = tex_data->Image;
use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
use_barrier[0].subresourceRange.levelCount = 1;
use_barrier[0].subresourceRange.layerCount = 1;
@ -769,28 +782,45 @@ bool LoadTextureFromFile(const char* filename, VkDescriptorSet* img_ds, int* ima
return true;
}
// Helper function to cleanup an image loaded with LoadTextureFromFile
void RemoveTexture(MyTextureData* tex_data)
{
vkFreeMemory(g_Device, tex_data->UploadBufferMemory, nullptr);
vkDestroyBuffer(g_Device, tex_data->UploadBuffer, nullptr);
vkDestroySampler(g_Device, tex_data->Sampler, nullptr);
vkDestroyImageView(g_Device, tex_data->ImageView, nullptr);
vkDestroyImage(g_Device, tex_data->Image, nullptr);
vkFreeMemory(g_Device, tex_data->ImageMemory, nullptr);
ImGui_ImplVulkan_RemoveTexture(tex_data->DS);
}
```
Load our texture after initializing Vulkan loader (for example after `ImGui_ImplVulkan_Init()`):
```cpp
int my_image_width = 0;
int my_image_height = 0;
VkDescriptorSet my_image_texture = 0;
bool ret = LoadTextureFromFile("../../MyImage01.jpg", &my_image_texture, &my_image_width, &my_image_height);
MyTextureData my_image_texture;
bool ret = LoadTextureFromFile("../../MyImage01.jpg", &my_image_texture);
IM_ASSERT(ret);
```
In the snippet of code above, we added an assert `IM_ASSERT(ret)` to check if the image file was loaded correctly. You may also use your Debugger and confirm that `my_image_texture` is not zero and that `my_image_width` `my_image_width` are correct.
In the snippet of code above, we added an assert `IM_ASSERT(ret)` to check if the image file was loaded correctly. You may also use your Debugger and confirm that `my_image_texture` is not zero and that `my_image_texture.Width` `my_image_texture.Height` are correct.
Now that we have an Vulkan texture and its dimensions, we can display it in our main loop:
```cpp
ImGui::Begin("Vulkan Texture Text");
ImGui::Text("pointer = %p", my_image_texture);
ImGui::Text("size = %d x %d", my_image_width, my_image_height);
ImGui::Image((ImTextureID)my_image_texture, ImVec2(my_image_width, my_image_height));
ImGui::Text("pointer = %p", my_image_texture.DS);
ImGui::Text("size = %d x %d", my_image_texture.Width, my_image_texture.Height);
ImGui::Image((ImTextureID)my_image_texture.DS, ImVec2(my_image_texture.Width, my_image_texture.Height));
ImGui::End();
```
In the cleanup stage remember to call the RemoveTexture function to avoid leaks, and angry Vulkan Validation Layers! Call it before `ImGui_ImplVulkan_Shutdown()`
```cpp
RemoveTexture(&my_image_texture);
```
// TODO replace with Vulkan image instead of this OpenGL placeholder
![image](https://user-images.githubusercontent.com/8225057/65344387-dfc21180-dbd7-11e9-9478-627403721435.png)