lsfg-vk/lsfg-vk-common/src/vulkan/vulkan.cpp

425 lines
19 KiB
C++

#include "lsfg-vk-common/vulkan/vulkan.hpp"
#include "lsfg-vk-common/helpers/errors.hpp"
#include "lsfg-vk-common/helpers/pointers.hpp"
#include <array>
#include <bitset>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include <dlfcn.h>
#include <vulkan/vk_layer.h>
#include <vulkan/vulkan_core.h>
using namespace vk;
namespace {
/// load libvulkan.so.1 and return its handle
void* get_vulkan_handle() {
static void* handle{nullptr}; // NOLINT
if (handle) return handle;
handle = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL);
if (!handle) handle = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
if (!handle)
throw ls::vulkan_error("failed to load libvulkan.so.1");
return handle;
}
/// get the main proc addr function
PFN_vkGetInstanceProcAddr get_mpa() {
static PFN_vkGetInstanceProcAddr mpa{nullptr}; // NOLINT
if (mpa) return mpa;
mpa = reinterpret_cast<PFN_vkGetInstanceProcAddr>(
dlsym(get_vulkan_handle(), "vkGetInstanceProcAddr"));
if (!mpa)
throw ls::vulkan_error("failed to get vkGetInstanceProcAddr symbol");
return mpa;
}
}
namespace {
template<typename T>
T ipa(PFN_vkGetInstanceProcAddr mpa, VkInstance instance, const char* name) {
T func = reinterpret_cast<T>(
mpa(instance, name));
if (!func)
throw ls::vulkan_error("failed to get instance proc addr for " + std::string(name));
return func;
}
/// create a vulkan instance
ls::owned_ptr<VkInstance> createInstance(
const std::string& appName, version appVersion,
const std::string& engineName, version engineVersion) {
VkInstance handle{};
auto vkCreateInstance =
ipa<PFN_vkCreateInstance>(get_mpa(), nullptr, "vkCreateInstance");
if (!vkCreateInstance)
throw ls::vulkan_error("failed to get vkCreateInstance symbol");
const VkApplicationInfo appInfo{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = appName.c_str(),
.applicationVersion = appVersion.into(),
.pEngineName = engineName.c_str(),
.engineVersion = engineVersion.into(),
.apiVersion = VK_API_VERSION_1_2 // seems 1.2 is supported on all Vulkan-capable GPUs
};
const VkInstanceCreateInfo instanceInfo{
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &appInfo
};
auto res = vkCreateInstance(&instanceInfo, nullptr, &handle);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkCreateInstance() failed");
auto defunc =
ipa<PFN_vkDestroyInstance>(get_mpa(), handle, "vkDestroyInstance");
if (!defunc)
throw ls::vulkan_error("failed to get vkDestroyInstance symbol");
return ls::owned_ptr<VkInstance>(
new VkInstance(handle),
[defunc](VkInstance& instance) {
defunc(instance, nullptr);
}
);
}
/// filter for a physical device
VkPhysicalDevice findPhysicalDevice(const VulkanInstanceFuncs& fi,
VkInstance instance,
PhysicalDeviceSelector filter) {
uint32_t phydevCount{};
auto res = fi.EnumeratePhysicalDevices(instance, &phydevCount, nullptr);
if (res != VK_SUCCESS || phydevCount == 0)
throw ls::vulkan_error(res, "vkEnumeratePhysicalDevices() failed");
std::vector<VkPhysicalDevice> phydevs(phydevCount);
res = fi.EnumeratePhysicalDevices(instance, &phydevCount, phydevs.data());
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkEnumeratePhysicalDevices() failed");
VkPhysicalDevice selected = filter(fi, phydevs);
if (!selected)
throw ls::vulkan_error("no suitable physical device found");
return selected;
}
/// find the queue family index with given flags
uint32_t findQFI(const VulkanInstanceFuncs& fi,
VkPhysicalDevice physdev, VkQueueFlags flags) {
uint32_t queueCount{};
fi.GetPhysicalDeviceQueueFamilyProperties(physdev, &queueCount, nullptr);
std::vector<VkQueueFamilyProperties> queues(queueCount);
fi.GetPhysicalDeviceQueueFamilyProperties(physdev, &queueCount, queues.data());
for (uint32_t i = 0; i < queueCount; ++i) {
if ((queues[i].queueFlags & flags) == flags)
return i;
}
throw ls::vulkan_error("no queue family with requested flags found");
}
/// check for fp16 support
bool checkFP16(const VulkanInstanceFuncs& fi, VkPhysicalDevice physdev) {
VkPhysicalDeviceVulkan12Features supportedFeaturesVulkan12{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES
};
VkPhysicalDeviceFeatures2 supportedFeatures{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &supportedFeaturesVulkan12
};
fi.GetPhysicalDeviceFeatures2(physdev, &supportedFeatures);
return supportedFeaturesVulkan12.shaderFloat16 == VK_TRUE;
}
template<typename T>
T dpa(const VulkanInstanceFuncs& funcs, VkDevice device, const char* name) {
T func = reinterpret_cast<T>(
funcs.GetDeviceProcAddr(device, name));
if (!func)
throw ls::vulkan_error("failed to get device proc addr for " + std::string(name));
return func;
}
/// create a logical device
ls::owned_ptr<VkDevice> createLogicalDevice(const VulkanInstanceFuncs& fi,
VkPhysicalDevice physdev, uint32_t cfi, bool fp16) {
VkDevice handle{};
const float queuePriority{1.0F}; // highest priority
const VkPhysicalDeviceVulkan12Features requestedFeaturesVulkan12{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.shaderFloat16 = fp16,
.timelineSemaphore = VK_TRUE,
.vulkanMemoryModel = VK_TRUE
};
const VkDeviceQueueCreateInfo requestedQueueInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = cfi,
.queueCount = 1,
.pQueuePriorities = &queuePriority
};
const std::vector<const char*> requestedExtensions{
VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME, // TODO: possibly attempt to get rid of
VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME
};
const VkDeviceCreateInfo deviceInfo{
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &requestedFeaturesVulkan12,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &requestedQueueInfo,
.enabledExtensionCount = static_cast<uint32_t>(requestedExtensions.size()),
.ppEnabledExtensionNames = requestedExtensions.data()
};
auto res = fi.CreateDevice(physdev, &deviceInfo, nullptr, &handle);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkCreateDevice() failed");
auto defunc =
dpa<PFN_vkDestroyDevice>(fi, handle, "vkDestroyDevice");
if (!defunc)
throw ls::vulkan_error("failed to get vkDestroyDevice symbol");
return ls::owned_ptr<VkDevice>(
new VkDevice(handle),
[defunc](VkDevice& device) {
defunc(device, nullptr);
}
);
}
/// get a queue from the logical device
VkQueue getQueue(const VulkanDeviceFuncs& fd, VkDevice device,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData,
uint32_t cfi) {
VkQueue queue{};
fd.GetDeviceQueue(device, cfi, 0, &queue);
if (setLoaderData) { // optionally set loader data
auto res = (*setLoaderData)(device, queue);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkSetDeviceLoaderData() failed");
}
return queue;
}
/// create a command pool
ls::owned_ptr<VkCommandPool> createCommandPool(const VulkanDeviceFuncs& fd,
VkDevice device, uint32_t cfi) {
VkCommandPool handle{};
const VkCommandPoolCreateInfo cmdpoolInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.queueFamilyIndex = cfi
};
auto res = fd.CreateCommandPool(device, &cmdpoolInfo, nullptr, &handle);
if (res != VK_SUCCESS)
throw ls::vulkan_error(res, "vkCreateCommandPool() failed");
return ls::owned_ptr<VkCommandPool>(
new VkCommandPool(handle),
[dev = device, defunc = fd.DestroyCommandPool](VkCommandPool& pool) {
defunc(dev, pool, nullptr);
}
);
}
}
/// initialize vulkan instance function pointers
VulkanInstanceFuncs vk::initVulkanInstanceFuncs(VkInstance i, PFN_vkGetInstanceProcAddr mpa,
bool graphical) {
return {
.DestroyInstance = ipa<PFN_vkDestroyInstance>(mpa, i, "vkDestroyInstance"),
.EnumeratePhysicalDevices = ipa<PFN_vkEnumeratePhysicalDevices>(mpa, i,
"vkEnumeratePhysicalDevices"),
.EnumerateDeviceExtensionProperties = ipa<PFN_vkEnumerateDeviceExtensionProperties>(mpa, i,
"vkEnumerateDeviceExtensionProperties"),
.GetPhysicalDeviceProperties2 = ipa<PFN_vkGetPhysicalDeviceProperties2>(mpa, i,
"vkGetPhysicalDeviceProperties2"),
.GetPhysicalDeviceQueueFamilyProperties =
ipa<PFN_vkGetPhysicalDeviceQueueFamilyProperties>(mpa, i,
"vkGetPhysicalDeviceQueueFamilyProperties"),
.GetPhysicalDeviceFeatures2 = graphical ?
nullptr : ipa<PFN_vkGetPhysicalDeviceFeatures2>(mpa, i, "vkGetPhysicalDeviceFeatures2"),
.GetPhysicalDeviceMemoryProperties = ipa<PFN_vkGetPhysicalDeviceMemoryProperties>(mpa, i,
"vkGetPhysicalDeviceMemoryProperties"),
.CreateDevice = ipa<PFN_vkCreateDevice>(mpa, i, "vkCreateDevice"),
.GetDeviceProcAddr = ipa<PFN_vkGetDeviceProcAddr>(mpa, i, "vkGetDeviceProcAddr"),
.GetPhysicalDeviceSurfaceCapabilitiesKHR = graphical ?
ipa<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(mpa, i,
"vkGetPhysicalDeviceSurfaceCapabilitiesKHR") : nullptr
};
}
/// initialize vulkan device function pointers
VulkanDeviceFuncs vk::initVulkanDeviceFuncs(const VulkanInstanceFuncs& f, VkDevice d,
bool graphical) {
return {
.GetDeviceQueue = dpa<PFN_vkGetDeviceQueue>(f, d, "vkGetDeviceQueue"),
.DeviceWaitIdle = dpa<PFN_vkDeviceWaitIdle>(f, d, "vkDeviceWaitIdle"),
.CreateCommandPool = dpa<PFN_vkCreateCommandPool>(f, d, "vkCreateCommandPool"),
.DestroyCommandPool = dpa<PFN_vkDestroyCommandPool>(f, d, "vkDestroyCommandPool"),
.CreateDescriptorPool = dpa<PFN_vkCreateDescriptorPool>(f, d, "vkCreateDescriptorPool"),
.DestroyDescriptorPool = dpa<PFN_vkDestroyDescriptorPool>(f, d, "vkDestroyDescriptorPool"),
.CreateBuffer = dpa<PFN_vkCreateBuffer>(f, d, "vkCreateBuffer"),
.DestroyBuffer = dpa<PFN_vkDestroyBuffer>(f, d, "vkDestroyBuffer"),
.GetBufferMemoryRequirements = dpa<PFN_vkGetBufferMemoryRequirements>(f, d,
"vkGetBufferMemoryRequirements"),
.AllocateMemory = dpa<PFN_vkAllocateMemory>(f, d, "vkAllocateMemory"),
.FreeMemory = dpa<PFN_vkFreeMemory>(f, d, "vkFreeMemory"),
.BindBufferMemory = dpa<PFN_vkBindBufferMemory>(f, d, "vkBindBufferMemory"),
.MapMemory = dpa<PFN_vkMapMemory>(f, d, "vkMapMemory"),
.UnmapMemory = dpa<PFN_vkUnmapMemory>(f, d, "vkUnmapMemory"),
.AllocateCommandBuffers = dpa<PFN_vkAllocateCommandBuffers>(f, d,
"vkAllocateCommandBuffers"),
.FreeCommandBuffers = dpa<PFN_vkFreeCommandBuffers>(f, d, "vkFreeCommandBuffers"),
.BeginCommandBuffer = dpa<PFN_vkBeginCommandBuffer>(f, d, "vkBeginCommandBuffer"),
.EndCommandBuffer = dpa<PFN_vkEndCommandBuffer>(f, d, "vkEndCommandBuffer"),
.CmdPipelineBarrier = dpa<PFN_vkCmdPipelineBarrier>(f, d, "vkCmdPipelineBarrier"),
.CmdBlitImage = dpa<PFN_vkCmdBlitImage>(f, d, "vkCmdBlitImage"),
.CmdClearColorImage = dpa<PFN_vkCmdClearColorImage>(f, d, "vkCmdClearColorImage"),
.CmdBindPipeline = dpa<PFN_vkCmdBindPipeline>(f, d, "vkCmdBindPipeline"),
.CmdBindDescriptorSets = dpa<PFN_vkCmdBindDescriptorSets>(f, d, "vkCmdBindDescriptorSets"),
.CmdDispatch = dpa<PFN_vkCmdDispatch>(f, d, "vkCmdDispatch"),
.CmdCopyBufferToImage = dpa<PFN_vkCmdCopyBufferToImage>(f, d, "vkCmdCopyBufferToImage"),
.QueueSubmit = dpa<PFN_vkQueueSubmit>(f, d, "vkQueueSubmit"),
.AllocateDescriptorSets = dpa<PFN_vkAllocateDescriptorSets>(f, d,
"vkAllocateDescriptorSets"),
.FreeDescriptorSets = dpa<PFN_vkFreeDescriptorSets>(f, d, "vkFreeDescriptorSets"),
.UpdateDescriptorSets = dpa<PFN_vkUpdateDescriptorSets>(f, d, "vkUpdateDescriptorSets"),
.CreateFence = dpa<PFN_vkCreateFence>(f, d, "vkCreateFence"),
.DestroyFence = dpa<PFN_vkDestroyFence>(f, d, "vkDestroyFence"),
.ResetFences = dpa<PFN_vkResetFences>(f, d, "vkResetFences"),
.WaitForFences = dpa<PFN_vkWaitForFences>(f, d, "vkWaitForFences"),
.CreateImage = dpa<PFN_vkCreateImage>(f, d, "vkCreateImage"),
.DestroyImage = dpa<PFN_vkDestroyImage>(f, d, "vkDestroyImage"),
.GetImageMemoryRequirements = dpa<PFN_vkGetImageMemoryRequirements>(f, d,
"vkGetImageMemoryRequirements"),
.BindImageMemory = dpa<PFN_vkBindImageMemory>(f, d, "vkBindImageMemory"),
.CreateImageView = dpa<PFN_vkCreateImageView>(f, d, "vkCreateImageView"),
.DestroyImageView = dpa<PFN_vkDestroyImageView>(f, d, "vkDestroyImageView"),
.CreateSampler = dpa<PFN_vkCreateSampler>(f, d, "vkCreateSampler"),
.DestroySampler = dpa<PFN_vkDestroySampler>(f, d, "vkDestroySampler"),
.CreateSemaphore = dpa<PFN_vkCreateSemaphore>(f, d, "vkCreateSemaphore"),
.DestroySemaphore = dpa<PFN_vkDestroySemaphore>(f, d, "vkDestroySemaphore"),
.CreateShaderModule = dpa<PFN_vkCreateShaderModule>(f, d, "vkCreateShaderModule"),
.DestroyShaderModule = dpa<PFN_vkDestroyShaderModule>(f, d, "vkDestroyShaderModule"),
.CreateDescriptorSetLayout = dpa<PFN_vkCreateDescriptorSetLayout>(f, d,
"vkCreateDescriptorSetLayout"),
.DestroyDescriptorSetLayout = dpa<PFN_vkDestroyDescriptorSetLayout>(f, d,
"vkDestroyDescriptorSetLayout"),
.CreatePipelineLayout = dpa<PFN_vkCreatePipelineLayout>(f, d, "vkCreatePipelineLayout"),
.DestroyPipelineLayout = dpa<PFN_vkDestroyPipelineLayout>(f, d, "vkDestroyPipelineLayout"),
.CreateComputePipelines = dpa<PFN_vkCreateComputePipelines>(f, d,
"vkCreateComputePipelines"),
.DestroyPipeline = dpa<PFN_vkDestroyPipeline>(f, d, "vkDestroyPipeline"),
.SignalSemaphoreKHR = dpa<PFN_vkSignalSemaphoreKHR>(f, d, "vkSignalSemaphoreKHR"),
.WaitSemaphoresKHR = dpa<PFN_vkWaitSemaphoresKHR>(f, d, "vkWaitSemaphoresKHR"),
.GetMemoryFdKHR = dpa<PFN_vkGetMemoryFdKHR>(f, d, "vkGetMemoryFdKHR"),
.ImportSemaphoreFdKHR = dpa<PFN_vkImportSemaphoreFdKHR>(f, d, "vkImportSemaphoreFdKHR"),
.GetSemaphoreFdKHR = dpa<PFN_vkGetSemaphoreFdKHR>(f, d, "vkGetSemaphoreFdKHR"),
.CreateSwapchainKHR = graphical ?
dpa<PFN_vkCreateSwapchainKHR>(f, d, "vkCreateSwapchainKHR") : nullptr,
.GetSwapchainImagesKHR = graphical ?
dpa<PFN_vkGetSwapchainImagesKHR>(f, d, "vkGetSwapchainImagesKHR") : nullptr,
.AcquireNextImageKHR = graphical ?
dpa<PFN_vkAcquireNextImageKHR>(f, d, "vkAcquireNextImageKHR") : nullptr,
.QueuePresentKHR = graphical ?
dpa<PFN_vkQueuePresentKHR>(f, d, "vkQueuePresentKHR") : nullptr,
.DestroySwapchainKHR = graphical ?
dpa<PFN_vkDestroySwapchainKHR>(f, d, "vkDestroySwapchainKHR") : nullptr
};
}
Vulkan::Vulkan(const std::string& appName, version appVersion,
const std::string& engineName, version engineVersion,
PhysicalDeviceSelector selectPhysicalDevice,
bool isGraphical,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData) :
instance(createInstance(
appName, appVersion,
engineName, engineVersion
)),
instance_funcs(initVulkanInstanceFuncs(*this->instance, get_mpa(), false)),
phys_dev(findPhysicalDevice(this->instance_funcs,
*this->instance,
selectPhysicalDevice
)),
queueFamilyIdx(findQFI(this->instance_funcs, this->phys_dev,
isGraphical ? VK_QUEUE_GRAPHICS_BIT : VK_QUEUE_COMPUTE_BIT)),
fp16(checkFP16(this->instance_funcs, this->phys_dev)),
device(createLogicalDevice(this->instance_funcs,
this->phys_dev,
this->queueFamilyIdx,
this->fp16
)),
setLoaderData(setLoaderData),
device_funcs(initVulkanDeviceFuncs(
this->instance_funcs,
*this->device, false
)),
computeQueue(getQueue(this->device_funcs, *this->device,
this->setLoaderData,
this->queueFamilyIdx)),
cmdPool(createCommandPool(this->device_funcs,
*this->device,
this->queueFamilyIdx
)) {
}
Vulkan::Vulkan(VkInstance instance, VkDevice device,
VkPhysicalDevice physdev,
VulkanInstanceFuncs instanceFuncs,
VulkanDeviceFuncs deviceFuncs,
bool isGraphical,
std::optional<PFN_vkSetDeviceLoaderData> setLoaderData) :
instance(new VkInstance(instance)),
instance_funcs(instanceFuncs),
phys_dev(physdev),
queueFamilyIdx(findQFI(this->instance_funcs, this->phys_dev,
isGraphical ? VK_QUEUE_GRAPHICS_BIT : VK_QUEUE_COMPUTE_BIT)),
fp16(false),
device(new VkDevice(device)),
setLoaderData(setLoaderData),
device_funcs(deviceFuncs),
computeQueue(getQueue(this->device_funcs, *this->device,
this->setLoaderData,
this->queueFamilyIdx)),
cmdPool(createCommandPool(this->device_funcs,
*this->device,
this->queueFamilyIdx
)) {
}
std::optional<uint32_t> Vulkan::findMemoryTypeIndex(
std::bitset<32> validTypes, bool hostVisibility) const {
const VkMemoryPropertyFlags desiredProps = hostVisibility ?
(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) :
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
VkPhysicalDeviceMemoryProperties props;
this->instance_funcs.GetPhysicalDeviceMemoryProperties(this->phys_dev, &props);
std::array<VkMemoryType, 32> memTypes = std::to_array(props.memoryTypes);
for (uint32_t i = 0; i < props.memoryTypeCount; ++i)
if (validTypes.test(i) && (memTypes.at(i).propertyFlags & desiredProps) == desiredProps)
return i;
return std::nullopt;
}