diff --git a/lsfg-vk-backend/src/utility/vkhelper.cpp b/lsfg-vk-backend/src/utility/vkhelper.cpp new file mode 100644 index 0000000..b350ea6 --- /dev/null +++ b/lsfg-vk-backend/src/utility/vkhelper.cpp @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "vkhelper.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Device initialization */ + +vk::UniqueInstance vkhelper::createInstance(vk::detail::DispatchLoaderDynamic& dld) { + dld.init(); + + const vk::ApplicationInfo appInfo{ + .pApplicationName = "lsfg-vk", + .applicationVersion = vk::makeVersion(2, 0, 0), + .pEngineName = "lsfg-vk", + .engineVersion = vk::makeVersion(2, 0, 0), + .apiVersion = vk::ApiVersion12 // Fully supported by all Vulkan-capable GPUs + }; + const vk::InstanceCreateInfo instanceInfo{ + .pApplicationInfo = &appInfo + }; + auto instance{vk::createInstanceUnique(instanceInfo, nullptr, dld)}; + dld.init(*instance); + + return instance; +} + +vk::PhysicalDevice vkhelper::findPhysicalDevice( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Instance& instance, + const std::string& id +) { + for (const auto& physdev : instance.enumeratePhysicalDevices(dld)) { + // Check for VK_EXT_pci_bus_info + bool supportsPCIEXT{false}; + for (const auto& ext : physdev.enumerateDeviceExtensionProperties(nullptr, dld)) { + if (std::string(ext.extensionName) != vk::EXTPciBusInfoExtensionName) + continue; + + supportsPCIEXT = true; + break; + } + + // Fetch properties + vk::PhysicalDevicePCIBusInfoPropertiesEXT busInfo{}; + vk::PhysicalDeviceProperties2 info{ + .pNext = supportsPCIEXT ? &busInfo : nullptr + }; + physdev.getProperties2(&info, dld); + + auto& props{info.properties}; + + // Compare device name + props.deviceName.back() = '\0'; // Ensure null-termination + if (id == std::string(props.deviceName)) + return physdev; + + // Compare Vendor ID + Device ID + std::ostringstream gpuss; + gpuss << std::hex << std::setfill('0') + << std::setw(4) << props.vendorID << ":" + << std::setw(4) << props.deviceID; + if (id == gpuss.str()) + return physdev; + + // Compare PCI bus ID + if (!supportsPCIEXT) + continue; + + std::ostringstream pciss; + pciss << std::hex << std::setfill('0') + << std::setw(4) << busInfo.pciDomain << ":" + << std::setw(2) << busInfo.pciBus << ":" + << std::setw(2) << busInfo.pciDevice << "." + << std::setw(1) << busInfo.pciFunction; + if (id == pciss.str()) + return physdev; + } + + throw std::runtime_error("No physical device matching '" + id + "' found"); +} + +uint32_t vkhelper::findComputeQueueFamilyIndex( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::PhysicalDevice& physdev +) { + uint32_t idx{0}; + for (const auto& qfi : physdev.getQueueFamilyProperties2(dld)) { + if (qfi.queueFamilyProperties.queueFlags & vk::QueueFlagBits::eCompute) + return idx; + idx++; + } + + throw std::runtime_error("No compute-capable queue family found"); +} + +bool vkhelper::checkHalfPrecisionSupport( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::PhysicalDevice& physdev +) { + vk::PhysicalDeviceVulkan12Features featuresVulkan12{}; + vk::PhysicalDeviceFeatures2 features{ + .pNext = &featuresVulkan12 + }; + physdev.getFeatures2(&features, dld); + return featuresVulkan12.shaderFloat16; +} + +std::pair vkhelper::createDevice( + vk::detail::DispatchLoaderDynamic& dld, + const vk::PhysicalDevice& physdev, + uint32_t qfi, + bool fp16 +) { + constexpr std::array EXTENSIONS{ + vk::KHRSynchronization2ExtensionName, + vk::KHRExternalMemoryFdExtensionName, + vk::KHRExternalSemaphoreFdExtensionName + }; + + vk::PhysicalDeviceSynchronization2FeaturesKHR sync2Info{ + .synchronization2 = VK_TRUE + }; + const vk::PhysicalDeviceVulkan12Features vk12Info{ + .pNext = &sync2Info, + .shaderFloat16 = fp16, + .timelineSemaphore = VK_TRUE + }; + const float queuePriority{1.0F}; // Highest priority + const vk::DeviceQueueCreateInfo queueInfo{ + .queueFamilyIndex = qfi, + .queueCount = 1, + .pQueuePriorities = &queuePriority + }; + const vk::DeviceCreateInfo deviceInfo{ + .pNext = &vk12Info, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queueInfo, + .enabledExtensionCount = static_cast(EXTENSIONS.size()), + .ppEnabledExtensionNames = EXTENSIONS.data() + }; + auto device{physdev.createDeviceUnique(deviceInfo, nullptr, dld)}; + dld.init(*device); + + return{ + std::move(device), + device->getQueue(qfi, 0, dld) + }; +} diff --git a/lsfg-vk-backend/src/utility/vkhelper.hpp b/lsfg-vk-backend/src/utility/vkhelper.hpp new file mode 100644 index 0000000..5dd9ed5 --- /dev/null +++ b/lsfg-vk-backend/src/utility/vkhelper.hpp @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ + +#pragma once + +#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 +#define VULKAN_HPP_NO_DEFAULT_DISPATCHER 1 +#define VULKAN_HPP_NO_CONSTRUCTORS 1 +#include // IWYU pragma: export + +// IWYU pragma: begin_exports +#include +#include +#include +#include +#include +#include +// IWYU pragma: end_exports + +#include +#include +#include + +namespace vkhelper { + + /* Device initialization */ + + /// + /// Create a Vulkan 1.2 instance for lsfg-vk + /// + /// @param dld Dynamic dispatch loader + /// @return RAII-wrapped Vulkan instance + /// @throws std::runtime_error on failure + /// + vk::UniqueInstance createInstance(vk::detail::DispatchLoaderDynamic& dld); + + /// + /// Find a physical device through a custom identifier + /// + /// The custom identifier may be one of: + /// - Device name (e.g. "NVIDIA GeForce RTX 5080") + /// - Vendor ID + Device ID in lowercase hexadecimal (e.g. "10de:2c02") + /// - PCI bus ID with padded zeroes (e.g. "0000:01:00.0") + /// + /// @param dld Dynamic dispatch loader + /// @param instance Vulkan instance + /// @param id Custom identifier + /// @return Selected physical device + /// @throws std::runtime_error if no suitable device found + /// + vk::PhysicalDevice findPhysicalDevice( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::Instance& instance, + const std::string& id + ); + + /// + /// Find the first compute-capable queue family index + /// + /// @param dld Dynamic dispatch loader + /// @param physdev Physical device + /// @return Queue family index + /// @throws std::runtime_error if no compute-capable queue found + /// + uint32_t findComputeQueueFamilyIndex( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::PhysicalDevice& physdev + ); + + /// + /// Check a physical device for half-precision float support + /// + /// @param dld Dynamic dispatch loader + /// @param physdev Physical device + /// @return Whether half-precision float is supported + /// + bool checkHalfPrecisionSupport( + const vk::detail::DispatchLoaderDynamic& dld, + const vk::PhysicalDevice& physdev + ); + + /// + /// Create a Vulkan device for lsfg-vk + /// + /// This device will have the core features timelineSemaphore and shaderFloat16 (if requested) + /// enabled, as well as the synchronization2, external memory & semaphore fd extensions. + /// + /// @param dld Dynamic dispatch loader + /// @param physdev Physical device + /// @param qfi Queue family index of compute-capable queue + /// @param fp16 Whether to enable half-precision float support + /// @return RAII-wrapped Vulkan device & compute queue + /// @throws std::runtime_error on failure + /// + std::pair createDevice( + vk::detail::DispatchLoaderDynamic& dld, + const vk::PhysicalDevice& physdev, + uint32_t qfi, + bool fp16 + ); + +}