From 504473bcaf51772ef5d12c17997f98a3d3f26244 Mon Sep 17 00:00:00 2001 From: DangitBen <30333928+DangitBen@users.noreply.github.com> Date: Wed, 9 Nov 2022 23:38:25 -0600 Subject: [PATCH] Revising Vulkan example to address leak --- Image-Loading-and-Displaying-Examples.md | 112 ++++++++++++++--------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/Image-Loading-and-Displaying-Examples.md b/Image-Loading-and-Displaying-Examples.md index cd7b89d..d2ad0a2 100644 --- a/Image-Loading-and-Displaying-Examples.md +++ b/Image-Loading-and-Displaying-Examples.md @@ -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, ®ion); + vkCmdCopyBufferToImage(command_buffer, tex_data->UploadBuffer, tex_data->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); 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)