Files
SDL3CPlusPlus/src/services/impl/vulkan_device_service.cpp

365 lines
14 KiB
C++

#include "vulkan_device_service.hpp"
#include <SDL3/SDL_vulkan.h>
#include <iostream>
#include <set>
#include <stdexcept>
#include <cstring>
namespace sdl3cpp::services::impl {
VulkanDeviceService::VulkanDeviceService(std::shared_ptr<ILogger> logger)
: logger_(logger) {
if (logger_) {
logger_->Trace("VulkanDeviceService", "VulkanDeviceService");
}
}
VulkanDeviceService::~VulkanDeviceService() {
if (logger_) {
logger_->Trace("VulkanDeviceService", "~VulkanDeviceService");
}
if (device_ != VK_NULL_HANDLE) {
Shutdown();
}
}
void VulkanDeviceService::Initialize(const std::vector<const char*>& deviceExtensions,
bool enableValidationLayers) {
logger_->Trace("VulkanDeviceService", "Initialize",
"deviceExtensions.size=" + std::to_string(deviceExtensions.size()) +
", enableValidationLayers=" + std::string(enableValidationLayers ? "true" : "false"));
if (instance_ != VK_NULL_HANDLE || surface_ != VK_NULL_HANDLE || device_ != VK_NULL_HANDLE) {
logger_->Warn("VulkanDeviceService::Initialize: Existing Vulkan handles detected; shutting down before reinitializing");
Shutdown();
}
deviceExtensions_ = deviceExtensions;
validationLayersEnabled_ = enableValidationLayers;
instance_ = VK_NULL_HANDLE;
surface_ = VK_NULL_HANDLE;
physicalDevice_ = VK_NULL_HANDLE;
device_ = VK_NULL_HANDLE;
graphicsQueue_ = VK_NULL_HANDLE;
presentQueue_ = VK_NULL_HANDLE;
// Get required extensions from SDL
uint32_t extensionCount = 0;
const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
if (!extensions) {
throw std::runtime_error("Failed to query Vulkan extensions from SDL");
}
std::vector<const char*> requiredExtensions(extensions, extensions + extensionCount);
CreateInstance(requiredExtensions);
logger_->Trace("VulkanDeviceService", "Initialize",
"instanceCreated=" + std::string(instance_ != VK_NULL_HANDLE ? "true" : "false") +
", selectionDeferredUntilSurface=true");
}
void VulkanDeviceService::CreateSurface(SDL_Window* window) {
logger_->Trace("VulkanDeviceService", "CreateSurface",
"windowIsNull=" + std::string(window ? "false" : "true"));
if (!window) {
throw std::invalid_argument("Window cannot be null");
}
if (!SDL_Vulkan_CreateSurface(window, instance_, nullptr, &surface_)) {
throw std::runtime_error("Failed to create Vulkan surface");
}
logger_->Trace("VulkanDeviceService", "CreateSurface",
"surfaceCreated=" + std::string(surface_ != VK_NULL_HANDLE ? "true" : "false"));
}
void VulkanDeviceService::CreateInstance(const std::vector<const char*>& requiredExtensions) {
logger_->Trace("VulkanDeviceService", "CreateInstance",
"requiredExtensions.size=" + std::to_string(requiredExtensions.size()));
// Check Vulkan availability
uint32_t apiVersion = 0;
VkResult enumResult = vkEnumerateInstanceVersion(&apiVersion);
if (enumResult != VK_SUCCESS) {
std::string errorMsg = "Vulkan is not available on this system.\n\n";
errorMsg += "Please install Vulkan drivers:\n";
errorMsg += "- Ubuntu/Debian: sudo apt install vulkan-tools libvulkan1\n";
errorMsg += "- Fedora: sudo dnf install vulkan-tools vulkan-loader\n";
errorMsg += "- Arch: sudo pacman -S vulkan-tools vulkan-icd-loader\n";
throw std::runtime_error(errorMsg);
}
uint32_t major = VK_API_VERSION_MAJOR(apiVersion);
uint32_t minor = VK_API_VERSION_MINOR(apiVersion);
uint32_t patch = VK_API_VERSION_PATCH(apiVersion);
std::cout << "Vulkan Version: " << major << "." << minor << "." << patch << "\n";
if (apiVersion < VK_API_VERSION_1_2) {
throw std::runtime_error("Vulkan 1.2 or higher required");
}
VkApplicationInfo appInfo{};
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
appInfo.pApplicationName = "SDL3 Vulkan";
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pEngineName = "No Engine";
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.apiVersion = VK_API_VERSION_1_2;
// Enable validation layers if requested
std::vector<const char*> layerList;
if (validationLayersEnabled_) {
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
std::vector<VkLayerProperties> availableLayers(layerCount);
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
const char* validationLayer = "VK_LAYER_KHRONOS_validation";
for (const auto& layer : availableLayers) {
if (strcmp(layer.layerName, validationLayer) == 0) {
layerList.push_back(validationLayer);
std::cout << "Validation layer enabled: " << validationLayer << "\n";
break;
}
}
}
VkInstanceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
createInfo.pApplicationInfo = &appInfo;
createInfo.enabledLayerCount = static_cast<uint32_t>(layerList.size());
createInfo.ppEnabledLayerNames = layerList.data();
createInfo.enabledExtensionCount = static_cast<uint32_t>(requiredExtensions.size());
createInfo.ppEnabledExtensionNames = requiredExtensions.data();
if (vkCreateInstance(&createInfo, nullptr, &instance_) != VK_SUCCESS) {
throw std::runtime_error("Failed to create Vulkan instance");
}
}
void VulkanDeviceService::PickPhysicalDevice() {
const bool instanceIsNull = instance_ == VK_NULL_HANDLE;
const bool surfaceIsNull = surface_ == VK_NULL_HANDLE;
logger_->Trace("VulkanDeviceService", "PickPhysicalDevice",
"instanceIsNull=" + std::string(instanceIsNull ? "true" : "false") +
", surfaceIsNull=" + std::string(surfaceIsNull ? "true" : "false"));
if (instanceIsNull) {
throw std::runtime_error("Vulkan instance must be created before selecting a physical device");
}
if (surfaceIsNull) {
throw std::runtime_error("Vulkan surface must be created before selecting a physical device");
}
uint32_t deviceCount = 0;
VkResult enumResult = vkEnumeratePhysicalDevices(instance_, &deviceCount, nullptr);
if (enumResult != VK_SUCCESS || deviceCount == 0) {
throw std::runtime_error("Failed to find GPUs with Vulkan support");
}
std::vector<VkPhysicalDevice> devices(deviceCount);
vkEnumeratePhysicalDevices(instance_, &deviceCount, devices.data());
std::cout << "\n=== GPU Detection ===\n";
for (size_t i = 0; i < devices.size(); ++i) {
VkPhysicalDeviceProperties props;
vkGetPhysicalDeviceProperties(devices[i], &props);
std::cout << "GPU " << i << ": " << props.deviceName << "\n";
if (IsDeviceSuitable(devices[i])) {
physicalDevice_ = devices[i];
std::cout << " -> SELECTED\n";
break;
} else {
std::cout << " -> UNSUITABLE\n";
}
}
std::cout << "==================\n\n";
if (physicalDevice_ == VK_NULL_HANDLE) {
throw std::runtime_error("Failed to find a suitable GPU");
}
}
bool VulkanDeviceService::IsDeviceSuitable(VkPhysicalDevice device) const {
const bool surfaceIsNull = surface_ == VK_NULL_HANDLE;
logger_->Trace("VulkanDeviceService", "IsDeviceSuitable",
"deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false") +
", surfaceIsNull=" + std::string(surfaceIsNull ? "true" : "false"));
if (surface_ == VK_NULL_HANDLE) {
throw std::runtime_error("Vulkan surface must be created before checking device suitability");
}
QueueFamilyIndices indices = FindQueueFamilies(device);
bool extensionsSupported = CheckDeviceExtensionSupport(device);
bool swapChainAdequate = false;
if (extensionsSupported) {
// Check swapchain support
VkSurfaceCapabilitiesKHR capabilities;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface_, &capabilities);
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &formatCount, nullptr);
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &presentModeCount, nullptr);
swapChainAdequate = formatCount > 0 && presentModeCount > 0;
}
return indices.IsComplete() && extensionsSupported && swapChainAdequate;
}
QueueFamilyIndices VulkanDeviceService::FindQueueFamilies(VkPhysicalDevice device) const {
const bool surfaceIsNull = surface_ == VK_NULL_HANDLE;
logger_->Trace("VulkanDeviceService", "FindQueueFamilies",
"deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false") +
", surfaceIsNull=" + std::string(surfaceIsNull ? "true" : "false"));
if (surface_ == VK_NULL_HANDLE) {
throw std::runtime_error("Vulkan surface must be created before querying queue families");
}
QueueFamilyIndices indices;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
for (uint32_t i = 0; i < queueFamilies.size(); ++i) {
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface_, &presentSupport);
if (presentSupport) {
indices.presentFamily = i;
}
if (indices.IsComplete()) {
break;
}
}
return indices;
}
bool VulkanDeviceService::CheckDeviceExtensionSupport(VkPhysicalDevice device) const {
logger_->Trace("VulkanDeviceService", "CheckDeviceExtensionSupport",
"deviceIsNull=" + std::string(device == VK_NULL_HANDLE ? "true" : "false"));
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());
std::set<std::string> requiredExtensions(deviceExtensions_.begin(), deviceExtensions_.end());
for (const auto& extension : availableExtensions) {
requiredExtensions.erase(extension.extensionName);
}
return requiredExtensions.empty();
}
void VulkanDeviceService::CreateLogicalDevice() {
logger_->Trace("VulkanDeviceService", "CreateLogicalDevice");
if (surface_ == VK_NULL_HANDLE) {
logger_->Trace("VulkanDeviceService", "CreateLogicalDevice", "surfaceIsNull=true");
throw std::runtime_error("Vulkan surface must be created before creating the logical device");
}
if (physicalDevice_ == VK_NULL_HANDLE) {
logger_->Trace("VulkanDeviceService", "CreateLogicalDevice", "physicalDeviceSelected=false");
PickPhysicalDevice();
}
logger_->Trace("VulkanDeviceService", "CreateLogicalDevice",
"physicalDeviceSelected=" + std::string(physicalDevice_ != VK_NULL_HANDLE ? "true" : "false"));
QueueFamilyIndices indices = FindQueueFamilies(physicalDevice_);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily, indices.presentFamily};
float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}
VkPhysicalDeviceFeatures deviceFeatures{};
VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.pEnabledFeatures = &deviceFeatures;
createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions_.size());
createInfo.ppEnabledExtensionNames = deviceExtensions_.data();
if (vkCreateDevice(physicalDevice_, &createInfo, nullptr, &device_) != VK_SUCCESS) {
throw std::runtime_error("Failed to create logical device");
}
logger_->Info("Logical device created successfully");
vkGetDeviceQueue(device_, indices.graphicsFamily, 0, &graphicsQueue_);
vkGetDeviceQueue(device_, indices.presentFamily, 0, &presentQueue_);
}
void VulkanDeviceService::Shutdown() noexcept {
logger_->Trace("VulkanDeviceService", "Shutdown");
if (device_ != VK_NULL_HANDLE) {
vkDestroyDevice(device_, nullptr);
device_ = VK_NULL_HANDLE;
}
if (surface_ != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(instance_, surface_, nullptr);
surface_ = VK_NULL_HANDLE;
}
if (instance_ != VK_NULL_HANDLE) {
vkDestroyInstance(instance_, nullptr);
instance_ = VK_NULL_HANDLE;
}
}
void VulkanDeviceService::WaitIdle() {
logger_->Trace("VulkanDeviceService", "WaitIdle");
if (device_ != VK_NULL_HANDLE) {
vkDeviceWaitIdle(device_);
}
}
QueueFamilyIndices VulkanDeviceService::GetQueueFamilies() const {
logger_->Trace("VulkanDeviceService", "GetQueueFamilies");
return FindQueueFamilies(physicalDevice_);
}
uint32_t VulkanDeviceService::FindMemoryType(uint32_t typeFilter,
VkMemoryPropertyFlags properties) const {
logger_->Trace("VulkanDeviceService", "FindMemoryType",
"typeFilter=" + std::to_string(typeFilter) +
", properties=" + std::to_string(static_cast<uint32_t>(properties)));
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice_, &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");
}
} // namespace sdl3cpp::services::impl