#include "nvrenderer_vulkan.h" #include #include #include "Globals.h" #include "Logs.h" #include "config.h" #include "nvrenderer/nvrenderer.h" #if LIBMANUL_WITH_VULKAN #include VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE #include "nvrendererbackend.h" #define MA_ERROR_LOG(fmt_str, ...) ErrorLog(fmt::format(fmt_str, __VA_ARGS__)) #define MA_INFO_LOG(fmt_str, ...) WriteLog(fmt::format(fmt_str, __VA_ARGS__)) #define CHECK(...) \ if (!(__VA_ARGS__)) { \ MA_ERROR_LOG("Initialization failure at {:s}", #__VA_ARGS__); \ return false; \ } namespace { std::vector stringSetToVector( const std::unordered_set& set) { std::vector ret; for (const auto& s : set) { ret.push_back(s.c_str()); } return ret; } template std::vector setToVector(const std::unordered_set& set) { std::vector ret; for (const auto& s : set) { ret.push_back(s); } return ret; } VKAPI_ATTR VkBool32 VKAPI_CALL vulkanDebugCallback( VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t obj, size_t location, int32_t code, const char* layerPrefix, // ReSharper disable once CppParameterMayBeConstPtrOrRef const char* msg, void* userData) { // auto manager = static_cast(userData); // if (manager) { // const auto& ignored = // manager->m_DeviceParams.ignoredVulkanValidationMessageLocations; // const auto found = std::find(ignored.begin(), ignored.end(), location); // if (found != ignored.end()) return VK_FALSE; // } if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { MA_ERROR_LOG("[Vulkan: location={:#x} code={:d}, layerPrefix='{:s}'] {:s}", location, code, layerPrefix, msg); // throw std::runtime_error(""); } else { MA_INFO_LOG("[Vulkan: location={:#x} code={:d}, layerPrefix='{:s}'] {:s}", location, code, layerPrefix, msg); } return VK_FALSE; } } // namespace bool NvRenderer::InitForVulkan(GLFWwindow* Window) { // Create d3d device const auto Backend = std::make_shared(); m_backend = Backend; Backend->m_Window = Window; Backend->m_renderer = this; Backend->m_BackBufferWidth = Global.window_size.x; Backend->m_BackBufferHeight = Global.window_size.y; //glfwGetWindowSize(Window, &Backend->m_BackBufferWidth, // &Backend->m_BackBufferHeight); Backend->m_EnableComputeQueue = true; Backend->m_EnableCopyQueue = true; Backend->m_EnableNvrhiValidationLayer = false; Backend->m_VsyncEnabled = Global.VSync; Backend->m_SwapChainBufferCount = 3; Backend->m_EnableDebugRuntime = Global.gfx_gldebug; if (!Backend->CreateInstance()) return false; if (!Backend->CreateDevice()) return false; if (!Backend->CreateSwapChain()) return false; Backend->m_BackBufferWidth = 0; Backend->m_BackBufferHeight = 0; Backend->UpdateWindowSize(); // if (!Backend->CreateDevice()) { // return false; // } // // if (!Backend->CreateSwapChain(Window)) { // return false; //} // // if (!Backend->CreateRenderTarget()) { // return false; //} return true; } nvrhi::IDevice* NvRendererBackend_Vulkan::GetDevice() { return m_EnableNvrhiValidationLayer ? m_ValidationLayer : m_NvrhiDevice; } std::string NvRendererBackend_Vulkan::LoadShaderBlob( const std::string& filename, const std::string& entrypoint) { return {}; } int NvRendererBackend_Vulkan::GetCurrentBackBufferIndex() { return m_SwapChainIndex; } int NvRendererBackend_Vulkan::GetBackBufferCount() { return static_cast(m_SwapChainImages.size()); } nvrhi::TextureHandle NvRendererBackend_Vulkan::GetBackBuffer(int index) { return m_SwapChainImages[index].rhiHandle; } bool NvRendererBackend_Vulkan::BeginFrame() { const auto& semaphore = m_PresentSemaphores[m_PresentSemaphoreIndex]; vk::Result res = vk::Result::eErrorUnknown; constexpr int maxAttempts = 3; for (int attempt = 0; attempt < maxAttempts; ++attempt) { res = m_VulkanDevice.acquireNextImageKHR( m_SwapChain, std::numeric_limits::max(), // timeout semaphore, vk::Fence(), &m_SwapChainIndex); if (res == vk::Result::eErrorOutOfDateKHR) { // BackBufferResizing(); auto surfaceCaps = m_VulkanPhysicalDevice.getSurfaceCapabilitiesKHR(m_WindowSurface); m_BackBufferWidth = static_cast(surfaceCaps.currentExtent.width); m_BackBufferHeight = static_cast(surfaceCaps.currentExtent.height); ResizeSwapChain(); // BackBufferResized(); } else break; } if (res == vk::Result::eSuccess) { m_NvrhiDevice->queueWaitForSemaphore(nvrhi::CommandQueue::Graphics, semaphore, 0); return true; } return false; } void NvRendererBackend_Vulkan::Present() { const auto& semaphore = m_PresentSemaphores[m_PresentSemaphoreIndex]; m_NvrhiDevice->queueSignalSemaphore(nvrhi::CommandQueue::Graphics, semaphore, 0); m_BarrierCommandList->open(); // umm... m_BarrierCommandList->close(); m_NvrhiDevice->executeCommandList(m_BarrierCommandList); const vk::PresentInfoKHR info = vk::PresentInfoKHR() .setWaitSemaphoreCount(1) .setPWaitSemaphores(&semaphore) .setSwapchainCount(1) .setPSwapchains(&m_SwapChain) .setPImageIndices(&m_SwapChainIndex); const vk::Result res = m_PresentQueue.presentKHR(&info); assert(res == vk::Result::eSuccess || res == vk::Result::eErrorOutOfDateKHR); m_PresentSemaphoreIndex = (m_PresentSemaphoreIndex + 1) % m_PresentSemaphores.size(); #ifndef _WIN32 if (m_VsyncEnabled) { m_PresentQueue.waitIdle(); } #endif while (m_FramesInFlight.size() >= m_MaxFramesInFlight) { auto query = m_FramesInFlight.front(); m_FramesInFlight.pop(); m_NvrhiDevice->waitEventQuery(query); m_QueryPool.push_back(query); } nvrhi::EventQueryHandle query; if (!m_QueryPool.empty()) { query = m_QueryPool.back(); m_QueryPool.pop_back(); } else { query = m_NvrhiDevice->createEventQuery(); } m_NvrhiDevice->resetEventQuery(query); m_NvrhiDevice->setEventQuery(query, nvrhi::CommandQueue::Graphics); m_FramesInFlight.push(query); } bool NvRendererBackend_Vulkan::IsHDRDisplay() const { return false; } bool NvRendererBackend_Vulkan::CreateInstance() { if (m_EnableDebugRuntime) { enabledExtensions.instance.insert("VK_EXT_debug_report"); enabledExtensions.layers.insert("VK_LAYER_KHRONOS_validation"); } const auto vkGetInstanceProcAddr = m_dynamicLoader.getProcAddress( "vkGetInstanceProcAddr"); VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); return createInstance(); } PFN_vkGetDeviceProcAddr NvRendererBackend_Vulkan::GetDeviceProcAddr() { return m_dynamicLoader.getProcAddress( "vkGetDeviceProcAddr"); } bool NvRendererBackend_Vulkan::CreateDevice() { if (m_EnableDebugRuntime) { installDebugCallback(); } std::vector requiredVulkanDeviceExtensions{ VK_EXT_SAMPLER_FILTER_MINMAX_EXTENSION_NAME}; // add device extensions requested by the user for (const std::string& name : requiredVulkanDeviceExtensions) { // enabledExtensions.device.insert(name); } // for (const std::string& name : // m_DeviceParams.optionalVulkanDeviceExtensions) { // optionalExtensions.device.insert(name); //} // if (!m_DeviceParams.headlessDevice) { // Need to adjust the swap chain format before creating the device because // it affects physical device selection // if (m_DeviceParams.swapChainFormat == nvrhi::Format::SRGBA8_UNORM) // m_DeviceParams.swapChainFormat = nvrhi::Format::SBGRA8_UNORM; // else if (m_DeviceParams.swapChainFormat == nvrhi::Format::RGBA8_UNORM) // m_DeviceParams.swapChainFormat = nvrhi::Format::BGRA8_UNORM; CHECK(createWindowSurface()) //} CHECK(pickPhysicalDevice()) CHECK(findQueueFamilies(m_VulkanPhysicalDevice)) CHECK(createDevice()) auto vecInstanceExt = stringSetToVector(enabledExtensions.instance); auto vecLayers = stringSetToVector(enabledExtensions.layers); auto vecDeviceExt = stringSetToVector(enabledExtensions.device); nvrhi::vulkan::DeviceDesc deviceDesc; deviceDesc.errorCB = GetMessageCallback(); deviceDesc.instance = m_VulkanInstance; deviceDesc.physicalDevice = m_VulkanPhysicalDevice; deviceDesc.device = m_VulkanDevice; deviceDesc.graphicsQueue = m_GraphicsQueue; deviceDesc.graphicsQueueIndex = m_GraphicsQueueFamily; if (m_EnableComputeQueue) { deviceDesc.computeQueue = m_ComputeQueue; deviceDesc.computeQueueIndex = m_ComputeQueueFamily; } if (m_EnableCopyQueue) { deviceDesc.transferQueue = m_TransferQueue; deviceDesc.transferQueueIndex = m_TransferQueueFamily; } deviceDesc.instanceExtensions = vecInstanceExt.data(); deviceDesc.numInstanceExtensions = vecInstanceExt.size(); deviceDesc.deviceExtensions = vecDeviceExt.data(); deviceDesc.numDeviceExtensions = vecDeviceExt.size(); deviceDesc.bufferDeviceAddressSupported = m_BufferDeviceAddressSupported; m_NvrhiDevice = nvrhi::vulkan::createDevice(deviceDesc); if (m_EnableNvrhiValidationLayer) { m_ValidationLayer = nvrhi::validation::createValidationLayer(m_NvrhiDevice); } return true; } bool NvRendererBackend_Vulkan::CreateSwapChain() { CHECK(createSwapChain()) m_BarrierCommandList = m_NvrhiDevice->createCommandList(); m_PresentSemaphores.reserve(m_MaxFramesInFlight + 1); for (uint32_t i = 0; i < m_MaxFramesInFlight + 1; ++i) { m_PresentSemaphores.push_back( m_VulkanDevice.createSemaphore(vk::SemaphoreCreateInfo())); } return true; } void NvRendererBackend_Vulkan::ResizeSwapChain() { if (m_VulkanDevice) { destroySwapChain(); createSwapChain(); } } void NvRendererBackend_Vulkan::UpdateWindowSize() { int width; int height; glfwGetWindowSize(m_Window, &width, &height); // if (width == 0 || height == 0) { // // window is minimized // m_windowVisible = false; // return; // } // // m_windowVisible = true; if (m_BackBufferWidth != width || m_BackBufferHeight != height) { // window is not minimized, and the size has changed // BackBufferResizing(); m_BackBufferWidth = width; m_BackBufferHeight = height; // m_DeviceParams.vsyncEnabled = m_RequestedVSync; ResizeSwapChain(); // BackBufferResized(); } // m_DeviceParams.vsyncEnabled = m_RequestedVSync; } bool NvRendererBackend_Vulkan::createInstance() { if (!glfwVulkanSupported()) { MA_ERROR_LOG( "{:s}", "GLFW reports that Vulkan is not supported. Perhaps missing a call to " "glfwInit()?"); return false; } // add any extensions required by GLFW uint32_t glfwExtCount; const char** glfwExt = glfwGetRequiredInstanceExtensions(&glfwExtCount); assert(glfwExt); for (uint32_t i = 0; i < glfwExtCount; i++) { enabledExtensions.instance.insert(std::string(glfwExt[i])); } // add instance extensions requested by the user // for (const std::string& name : // m_DeviceParams.requiredVulkanInstanceExtensions) { // enabledExtensions.instance.insert(name); //} // for (const std::string& name : // m_DeviceParams.optionalVulkanInstanceExtensions) { // optionalExtensions.instance.insert(name); //} // add layers requested by the user // for (const std::string& name : m_DeviceParams.requiredVulkanLayers) { // enabledExtensions.layers.insert(name); //} // for (const std::string& name : m_DeviceParams.optionalVulkanLayers) { // optionalExtensions.layers.insert(name); //} std::unordered_set requiredExtensions = enabledExtensions.instance; // figure out which optional extensions are supported for (const auto& instanceExt : vk::enumerateInstanceExtensionProperties()) { const std::string name = instanceExt.extensionName; if (optionalExtensions.instance.find(name) != optionalExtensions.instance.end()) { enabledExtensions.instance.insert(name); } requiredExtensions.erase(name); } if (!requiredExtensions.empty()) { std::stringstream ss; ss << "Cannot create a Vulkan instance because the following required " "extension(s) are not supported:"; for (const auto& ext : requiredExtensions) ss << std::endl << " - " << ext; MA_ERROR_LOG("{:s}", ss.str().c_str()); return false; } MA_INFO_LOG("{:s}", "Enabled Vulkan instance extensions:"); for (const auto& ext : enabledExtensions.instance) { MA_INFO_LOG(" {:s}", ext.c_str()); } std::unordered_set requiredLayers = enabledExtensions.layers; for (const auto& layer : vk::enumerateInstanceLayerProperties()) { const std::string name = layer.layerName; if (optionalExtensions.layers.find(name) != optionalExtensions.layers.end()) { enabledExtensions.layers.insert(name); } requiredLayers.erase(name); } if (!requiredLayers.empty()) { std::stringstream ss; ss << "Cannot create a Vulkan instance because the following required " "layer(s) are not supported:"; for (const auto& ext : requiredLayers) ss << std::endl << " - " << ext; MA_ERROR_LOG("{:s}", ss.str().c_str()); return false; } MA_INFO_LOG("{:s}", "Enabled Vulkan layers:"); for (const auto& layer : enabledExtensions.layers) { MA_INFO_LOG(" {:s}", layer.c_str()); } auto instanceExtVec = stringSetToVector(enabledExtensions.instance); auto layerVec = stringSetToVector(enabledExtensions.layers); auto applicationInfo = vk::ApplicationInfo(); // Query the Vulkan API version supported on the system to make sure we use at // least 1.3 when that's present. vk::Result res = vk::enumerateInstanceVersion(&applicationInfo.apiVersion); if (res != vk::Result::eSuccess) { MA_ERROR_LOG("Call to vkEnumerateInstanceVersion failed, error code = {:s}", nvrhi::vulkan::resultToString(static_cast(res))); return false; } constexpr uint32_t minimumVulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0); // Check if the Vulkan API version is sufficient. if (applicationInfo.apiVersion < minimumVulkanVersion) { MA_ERROR_LOG( "The Vulkan API version supported on the system ({:d}.{:d}.{:d}) is " "too low, " "at least {:d}.{:d}.{:d} is required.", VK_API_VERSION_MAJOR(applicationInfo.apiVersion), VK_API_VERSION_MINOR(applicationInfo.apiVersion), VK_API_VERSION_PATCH(applicationInfo.apiVersion), VK_API_VERSION_MAJOR(minimumVulkanVersion), VK_API_VERSION_MINOR(minimumVulkanVersion), VK_API_VERSION_PATCH(minimumVulkanVersion)); return false; } // Spec says: A non-zero variant indicates the API is a variant of the Vulkan // API and applications will typically need to be modified to run against it. if (VK_API_VERSION_VARIANT(applicationInfo.apiVersion) != 0) { MA_ERROR_LOG( "The Vulkan API supported on the system uses an unexpected variant: " "{:d}.", VK_API_VERSION_VARIANT(applicationInfo.apiVersion)); return false; } // Create the vulkan instance vk::InstanceCreateInfo info = vk::InstanceCreateInfo() .setEnabledLayerCount(static_cast(layerVec.size())) .setPpEnabledLayerNames(layerVec.data()) .setEnabledExtensionCount( static_cast(instanceExtVec.size())) .setPpEnabledExtensionNames(instanceExtVec.data()) .setPApplicationInfo(&applicationInfo); res = vk::createInstance(&info, nullptr, &m_VulkanInstance); if (res != vk::Result::eSuccess) { MA_ERROR_LOG("Failed to create a Vulkan instance, error code = {:s}", nvrhi::vulkan::resultToString(static_cast(res))); return false; } VULKAN_HPP_DEFAULT_DISPATCHER.init(m_VulkanInstance); return true; } bool NvRendererBackend_Vulkan::createWindowSurface() { if (const VkResult res = glfwCreateWindowSurface( m_VulkanInstance, m_Window, nullptr, reinterpret_cast(&m_WindowSurface)); res != VK_SUCCESS) { MA_ERROR_LOG("Failed to create a GLFW window surface, error code = {:s}", nvrhi::vulkan::resultToString(res)); return false; } return true; } void NvRendererBackend_Vulkan::installDebugCallback() { auto info = vk::DebugReportCallbackCreateInfoEXT() .setFlags(vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::eInformation | vk::DebugReportFlagBitsEXT::ePerformanceWarning) .setPfnCallback(&vulkanDebugCallback) .setPUserData(this); const vk::Result res = m_VulkanInstance.createDebugReportCallbackEXT( &info, nullptr, &m_DebugReportCallback); assert(res == vk::Result::eSuccess); } bool NvRendererBackend_Vulkan::pickPhysicalDevice() { VkFormat requestedFormat = nvrhi::vulkan::convertFormat(nvrhi::Format::BGRA8_UNORM); vk::Extent2D requestedExtent(m_BackBufferWidth, m_BackBufferHeight); auto devices = m_VulkanInstance.enumeratePhysicalDevices(); int firstDevice = 0; int lastDevice = static_cast(devices.size()) - 1; // if (m_DeviceParams.adapterIndex >= 0) { // if (m_DeviceParams.adapterIndex > lastDevice) { // log::error("The specified Vulkan physical device %d does not exist.", // m_DeviceParams.adapterIndex); // return false; // } // firstDevice = m_DeviceParams.adapterIndex; // lastDevice = m_DeviceParams.adapterIndex; // } // Start building an error message in case we cannot find a device. std::stringstream errorStream; errorStream << "Cannot find a Vulkan device that supports all the required " "extensions and properties."; // build a list of GPUs std::vector discreteGPUs; std::vector otherGPUs; for (int deviceIndex = firstDevice; deviceIndex <= lastDevice; ++deviceIndex) { vk::PhysicalDevice const& dev = devices[deviceIndex]; vk::PhysicalDeviceProperties prop = dev.getProperties(); errorStream << std::endl << prop.deviceName.data() << ":"; // check that all required device extensions are present std::unordered_set requiredExtensions = enabledExtensions.device; auto deviceExtensions = dev.enumerateDeviceExtensionProperties(); for (const auto& ext : deviceExtensions) { requiredExtensions.erase(std::string(ext.extensionName.data())); } bool deviceIsGood = true; if (!requiredExtensions.empty()) { // device is missing one or more required extensions for (const auto& ext : requiredExtensions) { errorStream << std::endl << " - missing " << ext; } deviceIsGood = false; } auto deviceFeatures = dev.getFeatures(); if (!deviceFeatures.samplerAnisotropy) { // device is a toaster oven errorStream << std::endl << " - does not support samplerAnisotropy"; deviceIsGood = false; } if (!deviceFeatures.textureCompressionBC) { errorStream << std::endl << " - does not support textureCompressionBC"; deviceIsGood = false; } if (!findQueueFamilies(dev)) { // device doesn't have all the queue families we need errorStream << std::endl << " - does not support the necessary queue types"; deviceIsGood = false; } if (deviceIsGood && m_WindowSurface) { if (bool surfaceSupported = dev.getSurfaceSupportKHR(m_PresentQueueFamily, m_WindowSurface); !surfaceSupported) { errorStream << std::endl << " - does not support the window surface"; deviceIsGood = false; } else { // check that this device supports our intended swap chain creation // parameters auto surfaceCaps = dev.getSurfaceCapabilitiesKHR(m_WindowSurface); auto surfaceFmts = dev.getSurfaceFormatsKHR(m_WindowSurface); if (surfaceCaps.minImageCount > m_SwapChainBufferCount || (surfaceCaps.maxImageCount < m_SwapChainBufferCount && surfaceCaps.maxImageCount > 0)) { errorStream << std::endl << " - cannot support the requested swap chain image count:"; errorStream << " requested " << m_SwapChainBufferCount << ", available " << surfaceCaps.minImageCount << " - " << surfaceCaps.maxImageCount; deviceIsGood = false; } if (surfaceCaps.minImageExtent.width > requestedExtent.width || surfaceCaps.minImageExtent.height > requestedExtent.height || surfaceCaps.maxImageExtent.width < requestedExtent.width || surfaceCaps.maxImageExtent.height < requestedExtent.height) { errorStream << std::endl << " - cannot support the requested swap chain size:"; errorStream << " requested " << requestedExtent.width << "x" << requestedExtent.height << ", "; errorStream << " available " << surfaceCaps.minImageExtent.width << "x" << surfaceCaps.minImageExtent.height; errorStream << " - " << surfaceCaps.maxImageExtent.width << "x" << surfaceCaps.maxImageExtent.height; deviceIsGood = false; } bool surfaceFormatPresent = false; for (const vk::SurfaceFormatKHR& surfaceFmt : surfaceFmts) { if (surfaceFmt.format == static_cast(requestedFormat)) { surfaceFormatPresent = true; break; } } if (!surfaceFormatPresent) { // can't create a swap chain using the format requested errorStream << std::endl << " - does not support the requested swap chain format"; deviceIsGood = false; } // check that we can present from the graphics queue if (uint32_t canPresent = dev.getSurfaceSupportKHR( m_GraphicsQueueFamily, m_WindowSurface); !canPresent) { errorStream << std::endl << " - cannot present"; deviceIsGood = false; } } } if (!deviceIsGood) continue; if (prop.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { discreteGPUs.push_back(dev); } else { otherGPUs.push_back(dev); } } // pick the first discrete GPU if it exists, otherwise the first integrated // GPU if (!discreteGPUs.empty()) { m_VulkanPhysicalDevice = discreteGPUs[0]; return true; } if (!otherGPUs.empty()) { m_VulkanPhysicalDevice = otherGPUs[0]; return true; } MA_ERROR_LOG("{:s}", errorStream.str().c_str()); return false; } bool NvRendererBackend_Vulkan::findQueueFamilies( vk::PhysicalDevice physicalDevice) { auto props = physicalDevice.getQueueFamilyProperties(); for (int i = 0; i < static_cast(props.size()); i++) { const auto& queueFamily = props[i]; if (m_GraphicsQueueFamily == -1) { if (queueFamily.queueCount > 0 && (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics)) { m_GraphicsQueueFamily = i; } } if (m_ComputeQueueFamily == -1) { if (queueFamily.queueCount > 0 && (queueFamily.queueFlags & vk::QueueFlagBits::eCompute) && !(queueFamily.queueFlags & vk::QueueFlagBits::eGraphics)) { m_ComputeQueueFamily = i; } } if (m_TransferQueueFamily == -1) { if (queueFamily.queueCount > 0 && (queueFamily.queueFlags & vk::QueueFlagBits::eTransfer) && !(queueFamily.queueFlags & vk::QueueFlagBits::eCompute) && !(queueFamily.queueFlags & vk::QueueFlagBits::eGraphics)) { m_TransferQueueFamily = i; } } if (m_PresentQueueFamily == -1) { if (queueFamily.queueCount > 0 && glfwGetPhysicalDevicePresentationSupport(m_VulkanInstance, physicalDevice, i)) { m_PresentQueueFamily = i; } } } if (m_GraphicsQueueFamily == -1 || m_PresentQueueFamily == -1 || (m_ComputeQueueFamily == -1 && m_EnableComputeQueue) || (m_TransferQueueFamily == -1 && m_EnableCopyQueue)) { return false; } return true; } bool NvRendererBackend_Vulkan::createDevice() { // figure out which optional extensions are supported auto deviceExtensions = m_VulkanPhysicalDevice.enumerateDeviceExtensionProperties(); for (const auto& ext : deviceExtensions) { const std::string name = ext.extensionName; if (optionalExtensions.device.find(name) != optionalExtensions.device.end()) { enabledExtensions.device.insert(name); } // if (m_DeviceParams.enableRayTracingExtensions && // m_RayTracingExtensions.find(name) != m_RayTracingExtensions.end()) { // enabledExtensions.device.insert(name); // } } // if (!m_DeviceParams.headlessDevice) { enabledExtensions.device.insert(VK_KHR_SWAPCHAIN_EXTENSION_NAME); //} const vk::PhysicalDeviceProperties physicalDeviceProperties = m_VulkanPhysicalDevice.getProperties(); m_RendererString = std::string(physicalDeviceProperties.deviceName.data()); bool accelStructSupported = false; bool rayPipelineSupported = false; bool rayQuerySupported = false; bool meshletsSupported = false; bool vrsSupported = false; bool synchronization2Supported = false; bool maintenance4Supported = false; MA_INFO_LOG("{:s}", "Enabled Vulkan device extensions:"); for (const auto& ext : enabledExtensions.device) { MA_INFO_LOG(" {:s}", ext.c_str()); if (ext == VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) accelStructSupported = true; else if (ext == VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME) rayPipelineSupported = true; else if (ext == VK_KHR_RAY_QUERY_EXTENSION_NAME) rayQuerySupported = true; else if (ext == VK_NV_MESH_SHADER_EXTENSION_NAME) meshletsSupported = true; else if (ext == VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME) vrsSupported = true; else if (ext == VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) synchronization2Supported = true; else if (ext == VK_KHR_MAINTENANCE_4_EXTENSION_NAME) maintenance4Supported = true; else if (ext == VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME) m_SwapChainMutableFormatSupported = true; } #define APPEND_EXTENSION(condition, desc) \ if (condition) { \ (desc).pNext = pNext; \ pNext = &(desc); \ } // NOLINT(cppcoreguidelines-macro-usage) void* pNext = nullptr; vk::PhysicalDeviceFeatures2 physicalDeviceFeatures2; // Determine support for Buffer Device Address, the Vulkan 1.2 way auto bufferDeviceAddressFeatures = vk::PhysicalDeviceBufferDeviceAddressFeatures(); // Determine support for maintenance4 auto maintenance4Features = vk::PhysicalDeviceMaintenance4Features(); // Put the user-provided extension structure at the end of the chain // pNext = m_DeviceParams.physicalDeviceFeatures2Extensions; APPEND_EXTENSION(true, bufferDeviceAddressFeatures); APPEND_EXTENSION(maintenance4Supported, maintenance4Features); physicalDeviceFeatures2.pNext = pNext; m_VulkanPhysicalDevice.getFeatures2(&physicalDeviceFeatures2); std::unordered_set uniqueQueueFamilies = {m_GraphicsQueueFamily}; // if (!m_DeviceParams.headlessDevice) uniqueQueueFamilies.insert(m_PresentQueueFamily); if (m_EnableComputeQueue) uniqueQueueFamilies.insert(m_ComputeQueueFamily); if (m_EnableCopyQueue) uniqueQueueFamilies.insert(m_TransferQueueFamily); float priority = 1.f; std::vector queueDesc; queueDesc.reserve(uniqueQueueFamilies.size()); for (int queueFamily : uniqueQueueFamilies) { queueDesc.push_back(vk::DeviceQueueCreateInfo() .setQueueFamilyIndex(queueFamily) .setQueueCount(1) .setPQueuePriorities(&priority)); } auto accelStructFeatures = vk::PhysicalDeviceAccelerationStructureFeaturesKHR() .setAccelerationStructure(true); auto rayPipelineFeatures = vk::PhysicalDeviceRayTracingPipelineFeaturesKHR() .setRayTracingPipeline(true) .setRayTraversalPrimitiveCulling(true); auto rayQueryFeatures = vk::PhysicalDeviceRayQueryFeaturesKHR().setRayQuery(true); auto meshletFeatures = vk::PhysicalDeviceMeshShaderFeaturesNV() .setTaskShader(true) .setMeshShader(true); auto vrsFeatures = vk::PhysicalDeviceFragmentShadingRateFeaturesKHR() .setPipelineFragmentShadingRate(true) .setPrimitiveFragmentShadingRate(true) .setAttachmentFragmentShadingRate(true); auto vulkan13features = vk::PhysicalDeviceVulkan13Features() .setSynchronization2(synchronization2Supported) .setMaintenance4(maintenance4Features.maintenance4); pNext = nullptr; APPEND_EXTENSION(accelStructSupported, accelStructFeatures) APPEND_EXTENSION(rayPipelineSupported, rayPipelineFeatures) APPEND_EXTENSION(rayQuerySupported, rayQueryFeatures) APPEND_EXTENSION(meshletsSupported, meshletFeatures) APPEND_EXTENSION(vrsSupported, vrsFeatures) APPEND_EXTENSION(physicalDeviceProperties.apiVersion >= VK_API_VERSION_1_3, vulkan13features) APPEND_EXTENSION(physicalDeviceProperties.apiVersion < VK_API_VERSION_1_3 && maintenance4Supported, maintenance4Features); #undef APPEND_EXTENSION auto deviceFeatures = vk::PhysicalDeviceFeatures() .setShaderImageGatherExtended(true) .setSamplerAnisotropy(true) .setTessellationShader(true) .setTextureCompressionBC(true) .setGeometryShader(true) .setImageCubeArray(true) .setShaderFloat64(true) .setShaderInt16(true) .setDualSrcBlend(true) .setMultiDrawIndirect(true); auto vulkan12features = vk::PhysicalDeviceVulkan12Features() .setDescriptorIndexing(true) .setRuntimeDescriptorArray(true) .setDescriptorBindingPartiallyBound(true) .setDescriptorBindingVariableDescriptorCount(true) .setTimelineSemaphore(true) .setShaderSampledImageArrayNonUniformIndexing(true) .setSamplerFilterMinmax(true) .setShaderFloat16(true) .setBufferDeviceAddress( bufferDeviceAddressFeatures.bufferDeviceAddress) .setPNext(pNext); auto layerVec = stringSetToVector(enabledExtensions.layers); auto extVec = stringSetToVector(enabledExtensions.device); auto deviceDesc = vk::DeviceCreateInfo() .setPQueueCreateInfos(queueDesc.data()) .setQueueCreateInfoCount(static_cast(queueDesc.size())) .setPEnabledFeatures(&deviceFeatures) .setEnabledExtensionCount(static_cast(extVec.size())) .setPpEnabledExtensionNames(extVec.data()) .setEnabledLayerCount(static_cast(layerVec.size())) .setPpEnabledLayerNames(layerVec.data()) .setPNext(&vulkan12features); // if (m_DeviceParams.deviceCreateInfoCallback) // m_DeviceParams.deviceCreateInfoCallback(deviceDesc); if (const vk::Result res = m_VulkanPhysicalDevice.createDevice( &deviceDesc, nullptr, &m_VulkanDevice); res != vk::Result::eSuccess) { MA_ERROR_LOG("Failed to create a Vulkan physical device, error code = {:s}", nvrhi::vulkan::resultToString(static_cast(res))); return false; } m_VulkanDevice.getQueue(m_GraphicsQueueFamily, 0, &m_GraphicsQueue); if (m_EnableComputeQueue) m_VulkanDevice.getQueue(m_ComputeQueueFamily, 0, &m_ComputeQueue); if (m_EnableCopyQueue) m_VulkanDevice.getQueue(m_TransferQueueFamily, 0, &m_TransferQueue); // if (!m_DeviceParams.headlessDevice) m_VulkanDevice.getQueue(m_PresentQueueFamily, 0, &m_PresentQueue); VULKAN_HPP_DEFAULT_DISPATCHER.init(m_VulkanDevice); // remember the bufferDeviceAddress feature enablement m_BufferDeviceAddressSupported = vulkan12features.bufferDeviceAddress; MA_INFO_LOG("Created Vulkan device: {:s}", m_RendererString.c_str()); return true; } bool NvRendererBackend_Vulkan::createSwapChain() { destroySwapChain(); m_SwapChainFormat = {static_cast(nvrhi::vulkan::convertFormat( nvrhi::Format::BGRA8_UNORM)), vk::ColorSpaceKHR::eSrgbNonlinear}; vk::Extent2D extent = vk::Extent2D(m_BackBufferWidth, m_BackBufferHeight); std::unordered_set uniqueQueues = { static_cast(m_GraphicsQueueFamily), static_cast(m_PresentQueueFamily)}; std::vector queues = setToVector(uniqueQueues); const bool enableSwapChainSharing = queues.size() > 1; auto desc = vk::SwapchainCreateInfoKHR() .setSurface(m_WindowSurface) .setMinImageCount(m_SwapChainBufferCount) .setImageFormat(m_SwapChainFormat.format) .setImageColorSpace(m_SwapChainFormat.colorSpace) .setImageExtent(extent) .setImageArrayLayers(1) .setImageUsage(vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled) .setImageSharingMode(enableSwapChainSharing ? vk::SharingMode::eConcurrent : vk::SharingMode::eExclusive) .setFlags(m_SwapChainMutableFormatSupported ? vk::SwapchainCreateFlagBitsKHR::eMutableFormat : static_cast(0)) .setQueueFamilyIndexCount( enableSwapChainSharing ? static_cast(queues.size()) : 0) .setPQueueFamilyIndices(enableSwapChainSharing ? queues.data() : nullptr) .setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity) .setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque) .setPresentMode(m_VsyncEnabled ? vk::PresentModeKHR::eFifo : vk::PresentModeKHR::eImmediate) .setClipped(true) .setOldSwapchain(nullptr); std::vector imageFormats = {m_SwapChainFormat.format}; switch (m_SwapChainFormat.format) { case vk::Format::eR8G8B8A8Unorm: imageFormats.push_back(vk::Format::eR8G8B8A8Srgb); break; case vk::Format::eR8G8B8A8Srgb: imageFormats.push_back(vk::Format::eR8G8B8A8Unorm); break; case vk::Format::eB8G8R8A8Unorm: imageFormats.push_back(vk::Format::eB8G8R8A8Srgb); break; case vk::Format::eB8G8R8A8Srgb: imageFormats.push_back(vk::Format::eB8G8R8A8Unorm); break; default:; } auto imageFormatListCreateInfo = vk::ImageFormatListCreateInfo().setViewFormats(imageFormats); if (m_SwapChainMutableFormatSupported) desc.pNext = &imageFormatListCreateInfo; if (const vk::Result res = m_VulkanDevice.createSwapchainKHR(&desc, nullptr, &m_SwapChain); res != vk::Result::eSuccess) { MA_ERROR_LOG("Failed to create a Vulkan swap chain, error code = {:s}", nvrhi::vulkan::resultToString(static_cast(res))); return false; } struct DisplayChromaticities { float RedX; float RedY; float GreenX; float GreenY; float BlueX; float BlueY; float WhiteX; float WhiteY; } Chromacities{0.70800f, 0.29200f, 0.17000f, 0.79700f, 0.13100f, 0.04600f, 0.31270f, 0.32900f}; //vk::HdrMetadataEXT hdr_metadata{{Chromacities.RedX, Chromacities.RedY}, // {Chromacities.GreenX, Chromacities.GreenY}, // {Chromacities.BlueX, Chromacities.BlueY}, // {Chromacities.WhiteX, Chromacities.WhiteY}, // 2500.f, // .001f, // 2000.f, // 500.f, // nullptr}; // m_VulkanDevice.setHdrMetadataEXT(1, &m_SwapChain, &hdr_metadata); // retrieve swap chain images auto images = m_VulkanDevice.getSwapchainImagesKHR(m_SwapChain); for (auto image : images) { SwapChainImage sci; sci.image = image; nvrhi::TextureDesc textureDesc; textureDesc.width = m_BackBufferWidth; textureDesc.height = m_BackBufferHeight; textureDesc.format = nvrhi::Format::BGRA8_UNORM; textureDesc.debugName = "Swap chain image"; textureDesc.initialState = nvrhi::ResourceStates::Present; textureDesc.keepInitialState = true; textureDesc.isRenderTarget = true; sci.rhiHandle = m_NvrhiDevice->createHandleForNativeTexture( nvrhi::ObjectTypes::VK_Image, nvrhi::Object(sci.image), textureDesc); m_SwapChainImages.push_back(sci); } m_framebuffers.clear(); for (int i = 0; i < GetBackBufferCount(); ++i) { m_framebuffers.emplace_back(GetDevice()->createFramebuffer( nvrhi::FramebufferDesc().addColorAttachment( nvrhi::FramebufferAttachment().setTexture(GetBackBuffer(i))))); } m_SwapChainIndex = 0; return true; } void NvRendererBackend_Vulkan::destroySwapChain() { if (m_VulkanDevice) { m_VulkanDevice.waitIdle(); } if (m_SwapChain) { m_VulkanDevice.destroySwapchainKHR(m_SwapChain); m_SwapChain = nullptr; } m_SwapChainImages.clear(); } #endif