text: /**
   * @package H2M
   * @author  Yan Chernikov (TheCherno)
   * @licence Apache License 2.0
   */
  
  #include "VulkanDeviceH2M.h"
  
  #include "VulkanContextH2M.h"
  #include "H2M/Core/AssertH2M.h"
  
  #include "Core/Log.h"
  
  
  namespace H2M
  {
  
  	////////////////////////////////////////////////////////////////////////////////////
  	// Vulkan Physical Device
  	////////////////////////////////////////////////////////////////////////////////////
  
  	VulkanPhysicalDeviceH2M::VulkanPhysicalDeviceH2M()
  	{
  		auto vkInstance = VulkanContextH2M::GetInstance();
  
  		uint32_t gpuCount = 0;
  		// Get number of available physical devices
  		vkEnumeratePhysicalDevices(vkInstance, &gpuCount, nullptr);
  		H2M_CORE_ASSERT(gpuCount > 0, "");
  		// Enumerate devices
  		std::vector<VkPhysicalDevice> physicalDevices(gpuCount);
  		VK_CHECK_RESULT_H2M(vkEnumeratePhysicalDevices(vkInstance, &gpuCount, physicalDevices.data()));
  
  		VkPhysicalDevice selectedPhysicalDevice = nullptr;
  		for (VkPhysicalDevice physicalDevice : physicalDevices)
  		{
  			vkGetPhysicalDeviceProperties(physicalDevice, &m_Properties);
  			if (m_Properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
  			{
  				selectedPhysicalDevice = physicalDevice;
  				break;
  			}
  		}
  
  		if (!selectedPhysicalDevice)
  		{
  			Log::GetLogger()->trace("Could not find discrete GPU.");
  			selectedPhysicalDevice = physicalDevices.back();
  		}
  
  		H2M_CORE_ASSERT(selectedPhysicalDevice, "Could not find any physical devices!");
  		m_PhysicalDevice = selectedPhysicalDevice;
  
  		// Device info
  		MORAVA_CORE_TRACE("=======================================");
  		MORAVA_CORE_TRACE(" Device info");
  		MORAVA_CORE_TRACE("   Device count: {0}", gpuCount);
  		MORAVA_CORE_TRACE("   Physical Device: {0}", m_Properties.deviceName);
  		MORAVA_CORE_TRACE("=======================================");
  
  		vkGetPhysicalDeviceFeatures(m_PhysicalDevice, &m_Features);
  		vkGetPhysicalDeviceMemoryProperties(m_PhysicalDevice, &m_MemoryProperties);
  
  		uint32_t queueFamilyCount;
  		vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, nullptr);
  		H2M_CORE_ASSERT(queueFamilyCount > 0, "");
  		m_QueueFamilyProperties.resize(queueFamilyCount);
  		vkGetPhysicalDeviceQueueFamilyProperties(m_PhysicalDevice, &queueFamilyCount, m_QueueFamilyProperties.data());
  
  		uint32_t extCount = 0;
  		vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &extCount, nullptr);
  		if (extCount > 0)
  		{
  			std::vector<VkExtensionProperties> extensions(extCount);
  			if (vkEnumerateDeviceExtensionProperties(m_PhysicalDevice, nullptr, &extCount, &extensions.front()) == VK_SUCCESS)
  			{
  				MORAVA_CORE_TRACE("Selected physical device has {0} extensions", extensions.size());
  				for (const auto& ext : extensions)
  				{
  					m_SupportedExtensions.emplace(ext.extensionName);
  					MORAVA_CORE_TRACE("  {0}", ext.extensionName);
  				}
  			}
  		}
  
  		// Queue families
  		// Desired queues need to be requested upon logical device creation
  		// Due to differing queue family configurations of Vulkan implementations this can be a bit tricky, especially if the application
  		// requests different queue types
  
  		// Get queue family indices for the requested queue family types
  		// Note that the indices may overlap depending on the implementation
  
  		static const float defaultQueuePriority(0.0f);
  
  		int requestedQueueTypes = VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT;
  		m_QueueFamilyIndices = GetQueueFamilyIndices(requestedQueueTypes);
  		
  		// Graphics queue
  		if (requestedQueueTypes & VK_QUEUE_GRAPHICS_BIT)
  		{
  			VkDeviceQueueCreateInfo queueInfo{};
  			queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
  			queueInfo.queueFamilyIndex = m_QueueFamilyIndices.Graphics;
  			queueInfo.queueCount = 1;
  			queueInfo.pQueuePriorities = &defaultQueuePriority;
  			m_QueueCreateInfos.push_back(queueInfo);
  		}
  
  		// Dedicated compute queue
  		if (requestedQueueTypes & VK_QUEUE_COMPUTE_BIT)
  		{
  			if (m_QueueFamilyIndices.Compute != m_QueueFamilyIndices.Graphics)
  			{
  				// If compute family index differs, we need an additional queue create info for the compute queue
  				VkDeviceQueueCreateInfo queueInfo{};
  				queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
  				queueInfo.queueFamilyIndex = m_QueueFamilyIndices.Compute;
  				queueInfo.queueCount = 1;
  				queueInfo.pQueuePriorities = &defaultQueuePriority;
  				m_QueueCreateInfos.push_back(queueInfo);
  			}
  		}
  
  		// Dedicated transfer queue
  		if (requestedQueueTypes & VK_QUEUE_TRANSFER_BIT)
  		{
  			if ((m_QueueFamilyIndices.Transfer != m_QueueFamilyIndices.Graphics) && (m_QueueFamilyIndices.Transfer != m_QueueFamilyIndices.Compute))
  			{
  				// If compute family index differs, we need an additional queue create info for the compute queue
  				VkDeviceQueueCreateInfo queueInfo{};
  				queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
  				queueInfo.queueFamilyIndex = m_QueueFamilyIndices.Transfer;
  				queueInfo.queueCount = 1;
  				queueInfo.pQueuePriorities = &defaultQueuePriority;
  				m_QueueCreateInfos.push_back(queueInfo);
  			}
  		}
  
  		m_DepthFormat = FindDepthFormat();
  		H2M_CORE_ASSERT(m_DepthFormat);
  	}
  
  	VulkanPhysicalDeviceH2M::~VulkanPhysicalDeviceH2M()
  	{
  	}
  
  	VkFormat VulkanPhysicalDeviceH2M::FindDepthFormat() const
  	{
  		// Since all depth formats may be optional, we need to find a suitable depth format to use
  		// Start with the highest precision packed format
  		std::vector<VkFormat> depthFormats = {
  			VK_FORMAT_D32_SFLOAT_S8_UINT,
  			VK_FORMAT_D32_SFLOAT,
  			VK_FORMAT_D24_UNORM_S8_UINT,
  			VK_FORMAT_D16_UNORM_S8_UINT,
  			VK_FORMAT_D16_UNORM
  		};
  
  		// TODO: Move to VulkanPhysicalDevice
  		for (auto& format : depthFormats)
  		{
  			VkFormatProperties formatProps;
  			vkGetPhysicalDeviceFormatProperties(m_PhysicalDevice, format, &formatProps);
  			// Format must support depth stencil attachment for optimal tiling
  			if (formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
  				return format;
  		}
  		return VK_FORMAT_UNDEFINED;
  	}
  
  	bool VulkanPhysicalDeviceH2M::IsExtensionSupported(const std::string& extensionName) const
  	{
  		return m_SupportedExtensions.find(extensionName) != m_SupportedExtensions.end();
  	}
  
  	VulkanPhysicalDeviceH2M::QueueFamilyIndicesH2M VulkanPhysicalDeviceH2M::GetQueueFamilyIndices(int flags)
  	{
  		QueueFamilyIndicesH2M indices;
  
  		// Dedicated queue for compute
  		// Try to find a queue family index that supports compute but not graphics
  		if (flags & VK_QUEUE_COMPUTE_BIT)
  		{
  			for (uint32_t i = 0; i < m_QueueFamilyProperties.size(); i++)
  			{
  				auto& queueFamilyProperties = m_QueueFamilyProperties[i];
  				if ((queueFamilyProperties.queueFlags & VK_QUEUE_COMPUTE_BIT) && ((queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0))
  				{
  					indices.Compute = i;
  					break;
  				}
  			}
  		}
  
  		// Dedicated queue for transfer
  		// Try to find a queue family index that supports transfer but not graphics and compute
  		if (flags & VK_QUEUE_TRANSFER_BIT)
  		{
  			for (uint32_t i = 0; i < m_QueueFamilyProperties.size(); i++)
  			{
  				auto& queueFamilyProperties = m_QueueFamilyProperties[i];
  				if ((queueFamilyProperties.queueFlags & VK_QUEUE_TRANSFER_BIT) && ((queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0) && ((queueFamilyProperties.queueFlags & VK_QUEUE_COMPUTE_BIT) == 0))
  				{
  					indices.Transfer = i;
  					break;
  				}
  			}
  		}
  
  		// For other queue types or if no separate compute queue is present, return the first one to support the requested flags
  		for (uint32_t i = 0; i < m_QueueFamilyProperties.size(); i++)
  		{
  			if ((flags & VK_QUEUE_TRANSFER_BIT) && indices.Transfer == -1)
  			{
  				if (m_QueueFamilyProperties[i].queueFlags & VK_QUEUE_TRANSFER_BIT)
  					indices.Transfer = i;
  			}
  
  			if ((flags & VK_QUEUE_COMPUTE_BIT) && indices.Compute == -1)
  			{
  				if (m_QueueFamilyProperties[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
  					indices.Compute = i;
  			}
  
  			if (flags & VK_QUEUE_GRAPHICS_BIT)
  			{
  				if (m_QueueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
  					indices.Graphics = i;
  			}
  		}
  
  		return indices;
  	}
  
  	uint32_t VulkanPhysicalDeviceH2M::GetMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) const
  	{
  		// Iterate over all memory types available for the device used in this example
  		for (uint32_t i = 0; i < m_MemoryProperties.memoryTypeCount; i++)
  		{
  			if ((typeBits & 1) == 1)
  			{
  				if ((m_MemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
  					return i;
  			}
  			typeBits >>= 1;
  		}
  
  		H2M_CORE_ASSERT(false, "Could not find a suitable memory type!");
  		return UINT32_MAX;
  	}
  
  	RefH2M<VulkanPhysicalDeviceH2M> VulkanPhysicalDeviceH2M::Select()
  	{
  		return RefH2M<VulkanPhysicalDeviceH2M>::Create();
  	}
  
  	////////////////////////////////////////////////////////////////////////////////////
  	// Vulkan Device
  	////////////////////////////////////////////////////////////////////////////////////
  
  	VulkanDeviceH2M::VulkanDeviceH2M(const RefH2M<VulkanPhysicalDeviceH2M>& physicalDevice, VkPhysicalDeviceFeatures enabledFeatures)
  		: m_PhysicalDevice(physicalDevice), m_EnabledFeatures(enabledFeatures)
  	{
  		// Do we need to enable any other extensions (eg. NV_RAYTRACING?)
  		std::vector<const char*> deviceExtensions;
  		// If the device will be used for presenting to a display via a swapchain we need to request the swapchain extension
  		H2M_CORE_ASSERT(m_PhysicalDevice->IsExtensionSupported(VK_KHR_SWAPCHAIN_EXTENSION_NAME));
  		deviceExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
  
  		VkDeviceCreateInfo deviceCreateInfo = {};
  		deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  		deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(physicalDevice->m_QueueCreateInfos.size());;
  		deviceCreateInfo.pQueueCreateInfos = physicalDevice->m_QueueCreateInfos.data();
  		deviceCreateInfo.pEnabledFeatures = &enabledFeatures;
  
  		// If a pNext(Chain) has been passed, we need to add it to the device creation info
  		VkPhysicalDeviceFeatures2 physicalDeviceFeatures2{};
  
  		// Enable the debug marker extension if it is present (likely meaning a debugging tool is present)
  		if (m_PhysicalDevice->IsExtensionSupported(VK_EXT_DEBUG_MARKER_EXTENSION_NAME))
  		{
  			deviceExtensions.push_back(VK_EXT_DEBUG_MARKER_EXTENSION_NAME);
  			m_EnableDebugMarkers = true;
  		}
  
  		if (deviceExtensions.size() > 0)
  		{
  			deviceCreateInfo.enabledExtensionCount = (uint32_t)deviceExtensions.size();
  			deviceCreateInfo.ppEnabledExtensionNames = deviceExtensions.data();
  		}
  
  		VkResult result = vkCreateDevice(m_PhysicalDevice->GetVulkanPhysicalDevice(), &deviceCreateInfo, nullptr, &m_LogicalDevice);
  		H2M_CORE_ASSERT(result == VK_SUCCESS);
  
  		VkCommandPoolCreateInfo cmdPoolInfo = {};
  		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
  		cmdPoolInfo.queueFamilyIndex = m_PhysicalDevice->m_QueueFamilyIndices.Graphics;
  		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  		VK_CHECK_RESULT_H2M(vkCreateCommandPool(m_LogicalDevice, &cmdPoolInfo, nullptr, &m_CommandPool));
  
  		cmdPoolInfo.queueFamilyIndex = m_PhysicalDevice->m_QueueFamilyIndices.Compute;
  		VK_CHECK_RESULT_H2M(vkCreateCommandPool(m_LogicalDevice, &cmdPoolInfo, nullptr, &m_ComputeCommandPool));
  
  		// Get a graphics queue from the device
  		vkGetDeviceQueue(m_LogicalDevice, m_PhysicalDevice->m_QueueFamilyIndices.Graphics, 0, &m_Queue);
  		vkGetDeviceQueue(m_LogicalDevice, m_PhysicalDevice->m_QueueFamilyIndices.Compute, 0, &m_ComputeQueue);
  	}
  
  	VulkanDeviceH2M::~VulkanDeviceH2M()
  	{
  	}
  
  	VkCommandBuffer VulkanDeviceH2M::GetCommandBuffer(bool begin, bool compute)
  	{
  		VkCommandBuffer cmdBuffer;
  
  		VkCommandBufferAllocateInfo cmdBufAllocateInfo = {};
  		cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  		cmdBufAllocateInfo.commandPool = compute ? m_ComputeCommandPool : m_CommandPool;
  		cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  		cmdBufAllocateInfo.commandBufferCount = 1;
  
  		VK_CHECK_RESULT_H2M(vkAllocateCommandBuffers(m_LogicalDevice, &cmdBufAllocateInfo, &cmdBuffer));
  
  		// If requested, also start the new command buffer
  		if (begin)
  		{
  			VkCommandBufferBeginInfo cmdBufferBeginInfo{};
  			cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  			VK_CHECK_RESULT_H2M(vkBeginCommandBuffer(cmdBuffer, &cmdBufferBeginInfo));
  		}
  
  		return cmdBuffer;
  	}
  
  	void VulkanDeviceH2M::FlushCommandBuffer(VkCommandBuffer commandBuffer)
  	{
  		FlushCommandBuffer(commandBuffer, m_Queue);
  	}
  
  	void VulkanDeviceH2M::FlushCommandBuffer(VkCommandBuffer commandBuffer, VkQueue queue)
  	{
  		const uint64_t DEFAULT_FENCE_TIMEOUT = 100000000000;
  
  		H2M_CORE_ASSERT(commandBuffer != VK_NULL_HANDLE);
  
  		VK_CHECK_RESULT_H2M(vkEndCommandBuffer(commandBuffer));
  
  		VkSubmitInfo submitInfo = {};
  		submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  		submitInfo.commandBufferCount = 1;
  		submitInfo.pCommandBuffers = &commandBuffer;
  
  		// Create fence to ensure that the command buffer has finished executing
  		VkFenceCreateInfo fenceCreateInfo = {};
  		fenceCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
  		fenceCreateInfo.flags = 0;
  		VkFence fence;
  		VK_CHECK_RESULT_H2M(vkCreateFence(m_LogicalDevice, &fenceCreateInfo, nullptr, &fence));
  
  		// Submit to the queue
  		VK_CHECK_RESULT_H2M(vkQueueSubmit(queue, 1, &submitInfo, fence));
  		// Wait for the fence to signal that command buffer has finished executing
  		VK_CHECK_RESULT_H2M(vkWaitForFences(m_LogicalDevice, 1, &fence, VK_TRUE, DEFAULT_FENCE_TIMEOUT));
  
  		vkDestroyFence(m_LogicalDevice, fence, nullptr);
  		vkFreeCommandBuffers(m_LogicalDevice, m_CommandPool, 1, &commandBuffer);
  	}
  
  	VkCommandBuffer VulkanDeviceH2M::CreateSecondaryCommandBuffer()
  	{
  		VkCommandBuffer cmdBuffer;
  
  		VkCommandBufferAllocateInfo cmdBufAllocateInfo = {};
  		cmdBufAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  		cmdBufAllocateInfo.commandPool = m_CommandPool;
  		cmdBufAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
  		cmdBufAllocateInfo.commandBufferCount = 1;
  
  		VK_CHECK_RESULT_H2M(vkAllocateCommandBuffers(m_LogicalDevice, &cmdBufAllocateInfo, &cmdBuffer));
  		return cmdBuffer;
  	}
  
  	/**
  	 * Buffer Helper Functions (VulkanGameEngine by Brendan Galea) 
  	 */
  	void VulkanDeviceH2M::CreateBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer& buffer, VkDeviceMemory& bufferMemory)
  	{
  		VkBufferCreateInfo bufferInfo{};
  		bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
  		bufferInfo.size = size;
  		bufferInfo.usage = usage;
  		bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  
  		if (vkCreateBuffer(m_LogicalDevice, &bufferInfo, nullptr, &buffer) != VK_SUCCESS) {
  			throw std::runtime_error("failed to create vertex buffer!");
  		}
  
  		VkMemoryRequirements memRequirements;
  		vkGetBufferMemoryRequirements(m_LogicalDevice, buffer, &memRequirements);
  
  		VkMemoryAllocateInfo allocInfo{};
  		allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
  		allocInfo.allocationSize = memRequirements.size;
  		allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties);
  
  		if (vkAllocateMemory(m_LogicalDevice, &allocInfo, nullptr, &bufferMemory) != VK_SUCCESS) {
  			throw std::runtime_error("failed to allocate vertex buffer memory!");
  		}
  
  		vkBindBufferMemory(m_LogicalDevice, buffer, bufferMemory, 0);
  	}
  
  	/**
  	 * Buffer Helper Functions (VulkanGameEngine by Brendan Galea)
  	 */
  	VkCommandBuffer VulkanDeviceH2M::BeginSingleTimeCommands()
  	{
  		VkCommandBufferAllocateInfo allocInfo{};
  		allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  		allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  		allocInfo.commandPool = m_CommandPool;
  		allocInfo.commandBufferCount = 1;
  
  		VkCommandBuffer commandBuffer;
  		vkAllocateCommandBuffers(m_LogicalDevice, &allocInfo, &commandBuffer);
  
  		VkCommandBufferBeginInfo beginInfo{};
  		beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  		beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
  
  		vkBeginCommandBuffer(commandBuffer, &beginInfo);
  		return commandBuffer;
  	}
  
  	/**
  	 * Buffer Helper Functions (VulkanGameEngine by Brendan Galea)
  	 */
  	void VulkanDeviceH2M::EndSingleTimeCommands(VkCommandBuffer commandBuffer)
  	{
  		vkEndCommandBuffer(commandBuffer);
  
  		VkSubmitInfo submitInfo{};
  		submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  		submitInfo.commandBufferCount = 1;
  		submitInfo.pCommandBuffers = &commandBuffer;
  
  		vkQueueSubmit(m_Queue, 1, &submitInfo, VK_NULL_HANDLE);
  		vkQueueWaitIdle(m_Queue);
  
  		vkFreeCommandBuffers(m_LogicalDevice, m_CommandPool, 1, &commandBuffer);
  	}
  
  	/**
  	 * Buffer Helper Functions (VulkanGameEngine by Brendan Galea)
  	 */
  	void VulkanDeviceH2M::CopyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
  	{
  		VkCommandBuffer commandBuffer = BeginSingleTimeCommands();
  
  		VkBufferCopy copyRegion{};
  		copyRegion.srcOffset = 0;  // Optional
  		copyRegion.dstOffset = 0;  // Optional
  		copyRegion.size = size;
  		vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
  
  		EndSingleTimeCommands(commandBuffer);
  	}
  
  	/**
  	 * Buffer Helper Functions (VulkanGameEngine by Brendan Galea)
  	 */
  	void VulkanDeviceH2M::CopyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t layerCount)
  	{
  		VkCommandBuffer commandBuffer = 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 = layerCount;
  
  		region.imageOffset = { 0, 0, 0 };
  		region.imageExtent = { width, height, 1 };
  
  		vkCmdCopyBufferToImage(
  			commandBuffer,
  			buffer,
  			image,
  			VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
  			1,
  			&region);
  		EndSingleTimeCommands(commandBuffer);
  	}
  
  	/**
  	 * Buffer Helper Functions (VulkanGameEngine by Brendan Galea)
  	 */
  	void VulkanDeviceH2M::CreateImageWithInfo(const VkImageCreateInfo& imageInfo, VkMemoryPropertyFlags properties, VkImage& image, VkDeviceMemory& imageMemory)
  	{
  		if (vkCreateImage(m_LogicalDevice, &imageInfo, nullptr, &image) != VK_SUCCESS)
  		{
  			throw std::runtime_error("failed to create image!");
  		}
  
  		VkMemoryRequirements memRequirements;
  		vkGetImageMemoryRequirements(m_LogicalDevice, image, &memRequirements);
  
  		VkMemoryAllocateInfo allocInfo{};
  		allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
  		allocInfo.allocationSize = memRequirements.size;
  		allocInfo.memoryTypeIndex = FindMemoryType(memRequirements.memoryTypeBits, properties);
  
  		if (vkAllocateMemory(m_LogicalDevice, &allocInfo, nullptr, &imageMemory) != VK_SUCCESS)
  		{
  			throw std::runtime_error("failed to allocate image memory!");
  		}
  
  		if (vkBindImageMemory(m_LogicalDevice, image, imageMemory, 0) != VK_SUCCESS)
  		{
  			throw std::runtime_error("failed to bind image memory!");
  		}
  	}
  
  	/**
  	 * Buffer Helper Functions (VulkanGameEngine by Brendan Galea)
  	 */
  	uint32_t VulkanDeviceH2M::FindMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
  	{
  		VkPhysicalDeviceMemoryProperties memProperties;
  		vkGetPhysicalDeviceMemoryProperties(m_PhysicalDevice->GetVulkanPhysicalDevice(), &memProperties);
  		for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
  		{
  			if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
  			{
  				return i;
  			}
  		}
  
  		throw std::runtime_error("failed to find suitable memory type!");
  	}
  
  } // END namespace Hazel
  