#include "VulkanRenderer.h" #include "graphics/2d.h" #include "libs/renderdoc/renderdoc.h" #include "mod_table/mod_table.h" #include "globalincs/version.h" #if SDL_VERSION_ATLEAST(2, 0, 6) #include #endif VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE namespace graphics { namespace vulkan { namespace { #if SDL_SUPPORTS_VULKAN const char* EngineName = "FreeSpaceOpen"; const gameversion::version MinVulkanVersion(1, 1, 0, 0); VkBool32 VKAPI_PTR debugReportCallback(VkDebugReportFlagsEXT /*flags*/, VkDebugReportObjectTypeEXT /*objectType*/, uint64_t /*object*/, size_t /*location*/, int32_t /*messageCode*/, const char* pLayerPrefix, const char* pMessage, void* /*pUserData*/) { mprintf(("Vulkan message: [%s]: %s\n", pLayerPrefix, pMessage)); return VK_FALSE; } #endif } // namespace VulkanRenderer::VulkanRenderer(std::unique_ptr graphicsOps) : m_graphicsOps(std::move(graphicsOps)) { } bool VulkanRenderer::initialize() { mprintf(("Initializing Vulkan graphics device at %ix%i with %i-bit color...\n", gr_screen.max_w, gr_screen.max_h, gr_screen.bits_per_pixel)); // Load the RenderDoc API if available before doing anything with OpenGL renderdoc::loadApi(); if (!initDisplayDevice()) { return false; } if (!initializeInstance()) { mprintf(("Failed to create Vulkan instance!\n")); return false; } if (!initializeSurface()) { mprintf(("Failed to create Vulkan surface!\n")); return false; } return true; } bool VulkanRenderer::initDisplayDevice() { os::ViewPortProperties attrs; attrs.enable_opengl = false; attrs.enable_vulkan = true; attrs.display = os_config_read_uint("Video", "Display", 0); attrs.width = (uint32_t)gr_screen.max_w; attrs.height = (uint32_t)gr_screen.max_h; attrs.title = Osreg_title; if (!Window_title.empty()) { attrs.title = Window_title; } if (Using_in_game_options) { switch (Gr_configured_window_state) { case os::ViewportState::Windowed: // That's the default break; case os::ViewportState::Borderless: attrs.flags.set(os::ViewPortFlags::Borderless); break; case os::ViewportState::Fullscreen: attrs.flags.set(os::ViewPortFlags::Fullscreen); break; } } else { if (!Cmdline_window && !Cmdline_fullscreen_window) { attrs.flags.set(os::ViewPortFlags::Fullscreen); } else if (Cmdline_fullscreen_window) { attrs.flags.set(os::ViewPortFlags::Borderless); } } m_viewPort = m_graphicsOps->createViewport(attrs); if (!m_viewPort) { return false; } return true; } bool VulkanRenderer::initializeInstance() { #if SDL_SUPPORTS_VULKAN const auto vkGetInstanceProcAddr = reinterpret_cast(SDL_Vulkan_GetVkGetInstanceProcAddr()); VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); const auto window = m_viewPort->toSDLWindow(); unsigned int count; if (!SDL_Vulkan_GetInstanceExtensions(window, &count, nullptr)) { mprintf(("Error in first SDL_Vulkan_GetInstanceExtensions: %s\n", SDL_GetError())); return false; } std::vector extensions; extensions.resize(count); if (!SDL_Vulkan_GetInstanceExtensions(window, &count, extensions.data())) { mprintf(("Error in second SDL_Vulkan_GetInstanceExtensions: %s\n", SDL_GetError())); return false; } const auto instanceVersion = vk::enumerateInstanceVersion(); gameversion::version vulkanVersion(VK_VERSION_MAJOR(instanceVersion), VK_VERSION_MINOR(instanceVersion), VK_VERSION_PATCH(instanceVersion), 0); mprintf(("Vulkan instance version %s\n", gameversion::format_version(vulkanVersion).c_str())); if (vulkanVersion < MinVulkanVersion) { mprintf(("Vulkan version is less than the minimum which is %s.\n", gameversion::format_version(MinVulkanVersion).c_str())); return false; } const auto supportedExtensions = vk::enumerateInstanceExtensionProperties(); mprintf(("Instance extensions:\n")); for (const auto& ext : supportedExtensions) { mprintf((" Found support for %s version %" PRIu32 "\n", ext.extensionName, ext.specVersion)); if (FSO_DEBUG || Cmdline_graphics_debug_output) { if (!stricmp(ext.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); m_debugReportEnabled = true; } } } std::vector layers; const auto supportedLayers = vk::enumerateInstanceLayerProperties(); mprintf(("Instance layers:\n")); for (const auto& layer : supportedLayers) { mprintf((" Found layer %s(%s). Spec version %" PRIu32 " and implementation %" PRIu32 "\n", layer.layerName, layer.description, layer.specVersion, layer.implementationVersion)); if (FSO_DEBUG || Cmdline_graphics_debug_output) { if (!stricmp(layer.layerName, "VK_LAYER_LUNARG_core_validation")) { layers.push_back("VK_LAYER_LUNARG_core_validation"); } } } vk::ApplicationInfo appInfo(Window_title.c_str(), 1, EngineName, 1, VK_API_VERSION_1_1); // Now we can make the Vulkan instance vk::InstanceCreateInfo createInfo(vk::Flags(), &appInfo); createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); createInfo.enabledLayerCount = static_cast(layers.size()); createInfo.ppEnabledLayerNames = layers.data(); vk::DebugReportCallbackCreateInfoEXT createInstanceReportInfo(vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning); createInstanceReportInfo.pfnCallback = debugReportCallback; vk::StructureChain createInstanceChain( createInfo, createInstanceReportInfo); if (!m_debugReportEnabled) { createInstanceChain.unlink(); } vk::UniqueInstance instance = vk::createInstanceUnique(createInstanceChain.get(), nullptr); if (!instance) { return false; } VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); if (m_debugReportEnabled) { vk::DebugReportCallbackCreateInfoEXT reportCreateInfo(vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning); reportCreateInfo.pfnCallback = debugReportCallback; m_debugReport = instance->createDebugReportCallbackEXTUnique(reportCreateInfo); } m_vkInstance = std::move(instance); return true; #else mprintf(("SDL does not support Vulkan in its current version.\n")); return false; #endif } bool VulkanRenderer::initializeSurface() { #if SDL_SUPPORTS_VULKAN const auto window = m_viewPort->toSDLWindow(); VkSurfaceKHR surface; if (!SDL_Vulkan_CreateSurface(window, static_cast(*m_vkInstance), &surface)) { mprintf(("Failed to create vulkan surface: %s\n", SDL_GetError())); return false; } vk::ObjectDestroy deleter(*m_vkInstance, nullptr, VULKAN_HPP_DEFAULT_DISPATCHER); m_vkSurface = vk::UniqueSurfaceKHR(vk::SurfaceKHR(surface), deleter); return true; #else return false; #endif } } // namespace vulkan } // namespace graphics