/*
VulkanFramework
by John Ryland
Copyright (c) 2023
*/
////////////////////////////////////////////////////////////////////////////////////
// Vulkan Device
#include "VulkanDevice.h"
#include <stdexcept>
#include <cstring>
#include <cstdio>
#include <cassert>
#include <memory>
//#ifdef __APPLE__
//#include "MoltenVK/vk_mvk_moltenvk.h"
//#endif
#ifdef NDEBUG
static const bool g_enableDebugReporting = false;
static const bool g_enableValidationLayers = false;
#else
static const bool g_enableDebugReporting = true;
static const bool g_enableValidationLayers = true;
#endif
namespace Vulkan {
Device::Device()
: m_instance(nullptr)
, m_device(nullptr)
, m_descriptorPool(nullptr)
, m_descriptorSetLayout(nullptr)
, m_commandPool(nullptr)
, m_debugReport(nullptr)
, m_allocator(nullptr)
, m_graphicsQueueFamily(0)
, m_graphicsQueue(nullptr)
, m_physicalDevice(nullptr)
{
}
// virtual
Device::~Device()
{
Destroy();
}
// virtual
void Device::Create(const char** extensions, uint32_t extensionsCount)
{
CreateInstance(extensions, extensionsCount, g_enableDebugReporting, g_enableValidationLayers);
SelectGPU();
DumpPhysicalDeviceExtensions();
SelectGraphicsQueueFamily();
CreateLogicalDevice();
vkGetDeviceQueue(m_device, m_graphicsQueueFamily, 0, &m_graphicsQueue);
CreateDescriptorPool();
CreateDescriptorSetLayout();
CreateCommandPool();
}
// virtual
void Device::Destroy()
{
if (m_debugReport)
{
// Remove the debug report callback
auto vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(m_instance, "vkDestroyDebugReportCallbackEXT");
vkDestroyDebugReportCallbackEXT(m_instance, m_debugReport, m_allocator);
}
if (m_commandPool)
vkDestroyCommandPool(m_device, m_commandPool, m_allocator);
if (m_descriptorSetLayout)
vkDestroyDescriptorSetLayout(m_device, m_descriptorSetLayout, m_allocator);
if (m_descriptorPool)
vkDestroyDescriptorPool(m_device, m_descriptorPool, m_allocator);
if (m_device)
vkDestroyDevice(m_device, m_allocator);
if (m_instance)
vkDestroyInstance(m_instance, m_allocator);
m_descriptorPool = nullptr;
m_descriptorSetLayout = nullptr;
m_commandPool = nullptr;
m_debugReport = nullptr;
m_device = nullptr;
m_instance = nullptr;
}
// virtual
void Device::CheckResult(VkResult error, const char* message)
{
if (error == VK_SUCCESS) {
fprintf(stderr, "[vulkan] Success: NOT %s \n", (message) ? message : "-");
return;
}
fprintf(stderr, "[vulkan] Error: VkResult = %d\n", error);
if (message)
fprintf(stderr, "[vulkan] Message: %s\n\n", message);
if (error < 0)
abort();
}
static VKAPI_ATTR
VkBool32 VKAPI_CALL DebugReport(VkDebugReportFlagsEXT flags,
VkDebugReportObjectTypeEXT objectType,
uint64_t object,
size_t location,
int32_t messageCode,
const char* pLayerPrefix,
const char* pMessage,
void* pUserData)
{
(void)flags; (void)object; (void)location; (void)messageCode; (void)pUserData; (void)pLayerPrefix; // Unused arguments
fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\n[vulkan] Message: %s\n\n", objectType, pMessage);
return VK_FALSE;
}
// virtual
void Device::CreateInstance(const char** extensions, uint32_t extensionsCount, bool enableDebug, bool enableValidationLayers)
{
const char* const validation_layers[] = {
"VK_LAYER_KHRONOS_validation"
};
VkInstanceCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
if (enableValidationLayers)
{
// Enabling validation layers
create_info.enabledLayerCount = 1;
create_info.ppEnabledLayerNames = validation_layers;
}
#if 1 // def __APPLE__
//create_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
// Enable debug report extension (we need additional storage, so we duplicate the user array to add our new extension to it)
// extensionsCount = 0;
auto extensions_ext = std::make_unique<const char *[]>(extensionsCount + 4);
memcpy(extensions_ext.get(), extensions, extensionsCount * sizeof(const char *));
//extensions_ext[extensionsCount + 0] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME;
//extensions_ext[extensionsCount + 1] = VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME;
//extensions_ext[extensionsCount + 2] = "VK_EXT_metal_surface";
extensions_ext[extensionsCount + 0] = "VK_EXT_debug_report";
extensions_ext[extensionsCount + 1] = "VK_KHR_portability_enumeration";
create_info.enabledExtensionCount = extensionsCount + 2;
create_info.ppEnabledExtensionNames = extensions_ext.get();
CheckResult(vkCreateInstance(&create_info, m_allocator, &m_instance), "failed to create instance!");
/*
MVKConfiguration config = {};
size_t siz = sizeof(config);
vkGetMoltenVKConfigurationMVK(m_instance, &config, &siz);
config.apiVersionToAdvertise = 310;
vkSetMoltenVKConfigurationMVK(m_instance, &config, &siz);
*/
// Get the function pointer (required for any extensions)
auto vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(m_instance, "vkCreateDebugReportCallbackEXT");
assert(vkCreateDebugReportCallbackEXT != nullptr);
// Setup the debug report callback
VkDebugReportCallbackCreateInfoEXT debug_report_ci = {};
debug_report_ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
debug_report_ci.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT
| VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
debug_report_ci.pfnCallback = DebugReport;
debug_report_ci.pUserData = nullptr;
CheckResult(vkCreateDebugReportCallbackEXT(m_instance, &debug_report_ci, m_allocator, &m_debugReport), "failed to register debug report callback!");
#else
if (enableDebug)
{
// Enable debug report extension (we need additional storage, so we duplicate the user array to add our new extension to it)
auto extensions_ext = std::make_unique<const char*[]>(extensionsCount + 1);
memcpy(extensions_ext.get(), extensions, extensionsCount * sizeof(const char*));
extensions_ext[extensionsCount] = "VK_EXT_debug_report";
extensions_ext[extensionsCount + 1] = "VK_KHR_portability_enumeration";
create_info.enabledExtensionCount = extensionsCount + 2;
create_info.ppEnabledExtensionNames = extensions_ext.get();
CheckResult(vkCreateInstance(&create_info, m_allocator, &m_instance), "failed to create instance!");
// Get the function pointer (required for any extensions)
auto vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(m_instance, "vkCreateDebugReportCallbackEXT");
assert(vkCreateDebugReportCallbackEXT != nullptr);
// Setup the debug report callback
VkDebugReportCallbackCreateInfoEXT debug_report_ci = {};
debug_report_ci.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
debug_report_ci.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
debug_report_ci.pfnCallback = DebugReport;
debug_report_ci.pUserData = nullptr;
CheckResult(vkCreateDebugReportCallbackEXT(m_instance, &debug_report_ci, m_allocator, &m_debugReport), "failed to register debug report callback!");
}
else
{
auto extensions_ext = std::make_unique<const char*[]>(extensionsCount + 1);
memcpy(extensions_ext.get(), extensions, extensionsCount * sizeof(const char*));
extensions_ext[extensionsCount] = "VK_KHR_portability_enumeration";
create_info.enabledExtensionCount = extensionsCount + 1;
create_info.ppEnabledExtensionNames = extensions_ext.get();
CheckResult(vkCreateInstance(&create_info, m_allocator, &m_instance), "failed to create instance!");
}
#endif
}
// virtual
void Device::SelectGPU()
{
uint32_t count;
CheckResult(vkEnumeratePhysicalDevices(m_instance, &count, nullptr));
CheckResult(count > 0 ? VK_SUCCESS : VK_ERROR_UNKNOWN, "no Vulkan supported devices found!"); // Ensure there is atleast one available
auto gpus = std::make_unique<VkPhysicalDevice[]>(count);
CheckResult(vkEnumeratePhysicalDevices(m_instance, &count, gpus.get()));
for (uint32_t i = 0; i < count; i++)
{
VkPhysicalDeviceProperties properties;
vkGetPhysicalDeviceProperties(gpus[i], &properties);
if (properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
{
m_physicalDevice = gpus[i]; // Use the 1st discrete gpu if available
return;
}
}
m_physicalDevice = gpus[0]; // Default to first gpu if no discrete gpus
}
// virtual
void Device::DumpPhysicalDeviceExtensions()
{
uint32_t extensionCount = 0;
const char* layerName = nullptr;
CheckResult(vkEnumerateDeviceExtensionProperties(m_physicalDevice, layerName, &extensionCount, nullptr));
auto extensions = std::make_unique<VkExtensionProperties[]>(extensionCount);
CheckResult(vkEnumerateDeviceExtensionProperties(m_physicalDevice, layerName, &extensionCount, extensions.get()));
printf("Extensions:\n");
for (uint32_t i = 0; i < extensionCount; i++)
{
printf(" - %s\n", extensions[i].extensionName);
}
}
// virtual
void Device::SelectGraphicsQueueFamily()
{
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &queueFamilyCount, nullptr);
auto queueFamilies = std::make_unique<VkQueueFamilyProperties[]>(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(m_physicalDevice, &queueFamilyCount, queueFamilies.get());
for (uint32_t i = 0; i < queueFamilyCount; i++)
{
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
{
m_graphicsQueueFamily = i;
return;
}
}
m_graphicsQueueFamily = (uint32_t)-1;
CheckResult(VK_ERROR_UNKNOWN, "no graphics queue family found!");
}
// virtual
void Device::CreateLogicalDevice()
{
const char* const device_extensions[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
"VK_KHR_portability_subset"
};
const float queue_priority[] = { 1.0f };
VkDeviceQueueCreateInfo queue_info[1] = {};
queue_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queue_info[0].queueFamilyIndex = m_graphicsQueueFamily;
queue_info[0].queueCount = 1;
queue_info[0].pQueuePriorities = queue_priority;
VkDeviceCreateInfo create_info = {};
create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
create_info.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]);
create_info.pQueueCreateInfos = queue_info;
create_info.enabledExtensionCount = 2;
create_info.ppEnabledExtensionNames = device_extensions;
CheckResult(vkCreateDevice(m_physicalDevice, &create_info, m_allocator, &m_device), "failed to create logical device!");
}
// virtual
void Device::CreateDescriptorPool()
{
static const uint32_t s_defaultPoolSets = 1000;
static const uint32_t s_defaultPoolSize = 1000;
VkDescriptorPoolSize pool_sizes[] =
{
{ VK_DESCRIPTOR_TYPE_SAMPLER, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, s_defaultPoolSize },
{ VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, s_defaultPoolSize }
};
VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
pool_info.maxSets = s_defaultPoolSets * std::size(pool_sizes);
pool_info.poolSizeCount = (uint32_t)std::size(pool_sizes);
pool_info.pPoolSizes = pool_sizes;
CheckResult(vkCreateDescriptorPool(m_device, &pool_info, m_allocator, &m_descriptorPool), "failed to create descriptor pool!");
}
// virtual
void Device::CreateDescriptorSetLayout()
{
VkDescriptorSetLayoutBinding uboLayoutBinding {};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.pImmutableSamplers = nullptr;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
VkDescriptorSetLayoutBinding samplerLayoutBinding {};
samplerLayoutBinding.binding = 1;
samplerLayoutBinding.descriptorCount = 1;
samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerLayoutBinding.pImmutableSamplers = nullptr;
samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayoutBinding bindings[2] = { uboLayoutBinding, samplerLayoutBinding };
VkDescriptorSetLayoutCreateInfo layoutInfo {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = static_cast<uint32_t>(std::size(bindings));
layoutInfo.pBindings = bindings;
CheckResult(vkCreateDescriptorSetLayout(m_device, &layoutInfo, m_allocator, &m_descriptorSetLayout), "failed to create descriptor set layout!");
}
// virtual
void Device::CreateCommandPool()
{
VkCommandPoolCreateInfo poolInfo {};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = m_graphicsQueueFamily;
CheckResult(vkCreateCommandPool(m_device, &poolInfo, m_allocator, &m_commandPool), "failed to create graphics command pool!");
}
// virtual
uint32_t Device::FindMemoryType(uint32_t typeFilter,
VkMemoryPropertyFlags properties)
{
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
{
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
return i;
}
CheckResult(VK_ERROR_UNKNOWN, "failed to find suitable memory type!");
return (uint32_t)-1;
}
// virtual
VkFormat Device::FindFormat(VkFormat* candidateFormats, size_t size, VkFormatFeatureFlags properties)
{
for (uint32_t i = 0; i < size; ++i)
{
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, candidateFormats[i], &props);
if ((props.optimalTilingFeatures & properties) == properties)
return candidateFormats[i];
}
CheckResult(VK_ERROR_UNKNOWN, "failed to find supported format!");
return VK_FORMAT_UNDEFINED;
}
Device::SingleTimeCommand Device::BeginSingleTimeCommands()
{
VkCommandBuffer commandBuffer;
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;
vkAllocateCommandBuffers(m_device, &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 SingleTimeCommand{ this, commandBuffer };
}
Device::SingleTimeCommand::~SingleTimeCommand()
{
vkEndCommandBuffer(m_commandBuffer);
VkSubmitInfo submitInfo {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &m_commandBuffer;
vkQueueSubmit(m_owner->m_graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(m_owner->m_graphicsQueue);
vkFreeCommandBuffers(m_owner->m_device, m_owner->m_commandPool, 1, &m_commandBuffer);
}
} // Vulkan namespace