mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-30 08:34:59 +00:00
365 lines
14 KiB
C++
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
|