/*
VulkanFramework
by John Ryland
Copyright (c) 2023
*/
////////////////////////////////////////////////////////////////////////////////////
// Vulkan Texture
#include "VulkanTexture2.h"
#include "VulkanBuffer.h"
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stdexcept>
#include <stb_image.h>
namespace Vulkan {
Texture::Texture()
: m_descriptorSet(nullptr)
, m_sampler(nullptr)
{
}
// virtual
Texture::~Texture()
{
Destroy();
}
// virtual
void Texture::Initialize(Device* device,
const char* fileName,
uint32_t mipLevels,
VkImageUsageFlags usage,
VkFormat imageFormat,
VkSampleCountFlagBits samples)
{
int texWidth, texHeight, texChannels;
stbi_uc* pixels = stbi_load(fileName, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
if (!pixels)
throw std::runtime_error("failed to load texture image!");
Initialize(device, pixels, texWidth, texHeight, mipLevels, usage, imageFormat, samples);
}
// virtual
void Texture::Initialize(Device* device,
uint8_t* pixels,
uint32_t width,
uint32_t height,
uint32_t mipLevels,
VkImageUsageFlags usage,
VkFormat imageFormat,
VkSampleCountFlagBits samples)
{
mipLevels = std::min(mipLevels, static_cast<uint32_t>(std::floor(std::log2(std::max(width, height)))) + 1);
VkDeviceSize imageSize = width * height * 4;
m_owner = device;
m_width = width;
m_height = height;
m_usage = usage;
m_aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
m_format = imageFormat;
m_mipLevels = mipLevels;
m_samples = samples;
Buffer staging;
staging.Initialize(device, imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
staging.UploadData(imageSize, pixels);
CreateImage();
TransitionImageLayout(VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
CopyBufferToImage(staging.m_buffer);
staging.Destroy();
CreateMipmaps();
CreateView();
CreateSampler();
}
// virtual
void Texture::Destroy()
{
if (m_descriptorSet)
vkFreeDescriptorSets(m_owner->m_device, m_owner->m_descriptorPool, 1, &m_descriptorSet);
if (m_sampler)
vkDestroySampler(m_owner->m_device, m_sampler, m_owner->m_allocator);
ImageBuffer::Destroy();
m_descriptorSet = nullptr;
m_sampler = nullptr;
}
// virtual
void Texture::CreateSampler()
{
VkPhysicalDeviceProperties properties {};
vkGetPhysicalDeviceProperties(m_owner->m_physicalDevice, &properties);
VkSamplerCreateInfo samplerInfo {};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; // outside image bounds uses border color
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.anisotropyEnable = VK_FALSE; // VK_TRUE; // TODO: this depends on the feature being enabled first
samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = static_cast<float>(m_mipLevels);
samplerInfo.mipLodBias = 0.0f;
m_owner->CheckResult(vkCreateSampler(m_owner->m_device, &samplerInfo, m_owner->m_allocator, &m_sampler));
}
// virtual
void Texture::TransitionImageLayout(VkImageLayout oldLayout, VkImageLayout newLayout)
{
auto commandBuffer = m_owner->BeginSingleTimeCommands();
VkImageMemoryBarrier barrier {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = m_image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = m_mipLevels;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;
if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
{
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
}
else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
}
else
{
throw std::invalid_argument("unsupported layout transition!");
}
vkCmdPipelineBarrier(commandBuffer.m_commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
// virtual
void Texture::CopyBufferToImage(VkBuffer buffer)
{
auto commandBuffer = m_owner->BeginSingleTimeCommands();
VkBufferImageCopy region {};
region.bufferOffset = 0;
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;
region.imageOffset = { 0, 0, 0 };
region.imageExtent = { m_width, m_height, 1 };
vkCmdCopyBufferToImage(commandBuffer.m_commandBuffer, buffer, m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
}
/*
#define MAX_FRAMES_IN_FLIGHT 2
// virtual
void Texture::CreateDescriptorSet()
{
std::vector<VkDescriptorSetLayout> layouts(MAX_FRAMES_IN_FLIGHT, m_owner->m_descriptorSetLayout);
VkDescriptorSetAllocateInfo allocInfo {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = m_owner->m_descriptorPool;
allocInfo.descriptorSetCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
allocInfo.pSetLayouts = layouts.data();
descriptorSets.resize(MAX_FRAMES_IN_FLIGHT);
m_owner->CheckResult(vkAllocateDescriptorSets(m_owner->m_device, &allocInfo, descriptorSets.data()), "failed to allocate descriptor sets!");
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
{
VkDescriptorBufferInfo bufferInfo {};
bufferInfo.buffer = m_uniformBuffers[i].m_buffer;
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);
VkDescriptorImageInfo imageInfo {};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = m_view;
imageInfo.sampler = m_sampler;
std::array<VkWriteDescriptorSet, 2> descriptorWrites {};
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = descriptorSets[i];
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &bufferInfo;
descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pImageInfo = &imageInfo;
vkUpdateDescriptorSets(m_owner->m_device,
static_cast<uint32_t>(descriptorWrites.size()),
descriptorWrites.data(),
0,
nullptr);
}
}
*/
// virtual
void Texture::CreateDescriptorSet()
{
VkDescriptorSetLayoutBinding binding{};
binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
binding.descriptorCount = 1;
binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayoutCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
info.bindingCount = 1;
info.pBindings = &binding;
m_owner->CheckResult(vkCreateDescriptorSetLayout(m_owner->m_device, &info, m_owner->m_allocator, &m_descriptorSetLayout));
VkDescriptorSetAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
alloc_info.descriptorPool = m_owner->m_descriptorPool;
alloc_info.descriptorSetCount = 1;
alloc_info.pSetLayouts = &m_descriptorSetLayout;
m_owner->CheckResult(vkAllocateDescriptorSets(m_owner->m_device, &alloc_info, &m_descriptorSet));
VkDescriptorImageInfo desc_image{};
desc_image.sampler = m_sampler;
desc_image.imageView = m_view;
desc_image.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
VkWriteDescriptorSet write_desc{};
write_desc.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_desc.dstSet = m_descriptorSet;
write_desc.descriptorCount = 1;
write_desc.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write_desc.pImageInfo = &desc_image;
vkUpdateDescriptorSets(m_owner->m_device, 1, &write_desc, 0, nullptr);
}
} // Vulkan namespace
#if 0
void createTexture()
{
int texWidth, texHeight, texChannels;
stbi_uc* pixels = stbi_load(("../vulkan/" + TEXTURE_PATH).c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
VkDeviceSize imageSize = texWidth * texHeight * 4;
mipLevels = static_cast<uint32_t>(std::floor(std::log2(std::max(texWidth, texHeight)))) + 1;
if (!pixels)
{
throw std::runtime_error("failed to load texture image!");
}
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
void* data;
vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
memcpy(data, pixels, static_cast<size_t>(imageSize));
vkUnmapMemory(device, stagingBufferMemory);
stbi_image_free(pixels);
createImage(
texWidth,
texHeight,
mipLevels,
VK_SAMPLE_COUNT_1_BIT,
VK_FORMAT_R8G8B8A8_SRGB,
VK_IMAGE_TILING_OPTIMAL,
VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
textureImage,
textureImageMemory);
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels);
copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
// transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
// while generating mipmaps
vkDestroyBuffer(device, stagingBuffer, nullptr);
vkFreeMemory(device, stagingBufferMemory, nullptr);
generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_SRGB, texWidth, texHeight, mipLevels);
textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, mipLevels);
createTextureSampler();
}
// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem, please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions.
VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout)
{
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
// Create Descriptor Set:
VkDescriptorSet descriptor_set;
{
VkDescriptorSetAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
alloc_info.descriptorPool = v->DescriptorPool;
alloc_info.descriptorSetCount = 1;
alloc_info.pSetLayouts = &bd->DescriptorSetLayout;
VkResult err = vkAllocateDescriptorSets(v->Device, &alloc_info, &descriptor_set);
check_vk_result(err);
}
// Update the Descriptor Set:
{
VkDescriptorImageInfo desc_image[1] = {};
desc_image[0].sampler = sampler;
desc_image[0].imageView = image_view;
desc_image[0].imageLayout = image_layout;
VkWriteDescriptorSet write_desc[1] = {};
write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_desc[0].dstSet = descriptor_set;
write_desc[0].descriptorCount = 1;
write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write_desc[0].pImageInfo = desc_image;
vkUpdateDescriptorSets(v->Device, 1, write_desc, 0, nullptr);
}
return descriptor_set;
}
void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set)
{
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
vkFreeDescriptorSets(v->Device, v->DescriptorPool, 1, &descriptor_set);
}
// Helper function to cleanup an image loaded with LoadTextureFromFile
void VulkanRendererDetails::UnloadTexture(VulkanTexture* tex_data)
{
ImGui_ImplVulkan_RemoveTexture(tex_data->DS);
}
static
void UploadVulkanImage(VkDevice dev, VkPhysicalDevice phyDev, const VkAllocationCallbacks* allocator, VulkanTexture* tex_data, unsigned char* image_data, VkCommandBuffer command_buffer)
{
// Calculate allocation size (in number of bytes)
size_t image_size = tex_data->Width * tex_data->Height * tex_data->Channels;
VkResult err;
// Create Upload Buffer
{
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(dev, &buffer_info, allocator, &tex_data->UploadBuffer);
check_vk_result(err);
VkMemoryRequirements req;
vkGetBufferMemoryRequirements(dev, tex_data->UploadBuffer, &req);
// TODO:
//bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment;
VkMemoryAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.allocationSize = req.size;
alloc_info.memoryTypeIndex = FindMemoryType_Impl(phyDev, req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
err = vkAllocateMemory(dev, &alloc_info, allocator, &tex_data->UploadBufferMemory);
check_vk_result(err);
err = vkBindBufferMemory(dev, tex_data->UploadBuffer, tex_data->UploadBufferMemory, 0);
check_vk_result(err);
}
// Upload to Buffer:
{
void* map = NULL;
err = vkMapMemory(dev, 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 = tex_data->UploadBufferMemory;
range[0].size = image_size;
err = vkFlushMappedMemoryRanges(dev, 1, range);
check_vk_result(err);
vkUnmapMemory(dev, tex_data->UploadBufferMemory);
}
// Copy to Image
{
VkImageMemoryBarrier copy_barrier[1] = {};
copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
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 = 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;
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier);
VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = tex_data->Width;
region.imageExtent.height = tex_data->Height;
region.imageExtent.depth = 1;
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;
use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
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 = 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;
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier);
}
}
bool VulkanRendererDetails::LoadTextureFromImage(Image& textureImage, VulkanTexture* tex_data)
{
unsigned char* image_data = textureImage.Data();
if (image_data == NULL)
return false;
tex_data->Width = textureImage.Width();
tex_data->Height = textureImage.Height();
tex_data->Channels = 4;
VkCommandPool command_pool = m_MainWindowData.Frames[m_MainWindowData.FrameIndex].CommandPool;
// Create a command buffer that will perform following steps when hit in the command queue.
// TODO: this works in the example, but may need input if this is an acceptable way to access the pool/create the command buffer.
VkCommandBuffer command_buffer;
{
VkCommandBufferAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandPool = command_pool;
alloc_info.commandBufferCount = 1;
VkResult err = vkAllocateCommandBuffers(m_Device, &alloc_info, &command_buffer);
check_vk_result(err);
VkCommandBufferBeginInfo begin_info = {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
err = vkBeginCommandBuffer(command_buffer, &begin_info);
check_vk_result(err);
}
CreateVulkanImage(m_Device, m_PhysicalDevice, m_Allocator, tex_data, true);
// Create Descriptor Set using ImGUI's implementation
tex_data->DS = ImGui_ImplVulkan_AddTexture(tex_data->Sampler, tex_data->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
UploadVulkanImage(m_Device, m_PhysicalDevice, m_Allocator, tex_data, image_data, command_buffer);
// Release image memory using stb
//stbi_image_free(image_data);
// End command buffer
{
VkSubmitInfo end_info = {};
end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
end_info.commandBufferCount = 1;
end_info.pCommandBuffers = &command_buffer;
VkResult err = vkEndCommandBuffer(command_buffer);
check_vk_result(err);
err = vkQueueSubmit(m_Queue, 1, &end_info, VK_NULL_HANDLE);
check_vk_result(err);
err = vkDeviceWaitIdle(m_Device);
check_vk_result(err);
}
return true;
}
#endif