17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 14:29:20 +02:00
Files
maszyna/betterRenderer/renderer/source/nvrenderer_vulkan.cpp
maj00r 26501b0fd7 hoist standard includes above everything else
Ordering them before the imports in the same file was not enough: a
project header on line 1 can import a module several levels down whose
global module fragment already pulled in the same standard header, and
that include guard does not cross the module boundary. Putting them
first means the guard is set before anything can import.

Skips vendored single-header libraries and anything inside an #if.
2026-09-10 01:02:17 +02:00

1100 lines
38 KiB
C++

#include <cstddef>
#include <cstdint>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <vector>
#include "nvrenderer_vulkan.h"
#include <GLFW/glfw3.h>
#include <fmt/format.h>
#include "utilities/Globals_macros.h"
#include "config.h"
#include "nvrenderer/nvrenderer.h"
#if LIBMANUL_WITH_VULKAN
#include <nvrhi/validation.h>
import eu07.utilities.globals;
import eu07.utilities.logs;
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
#include "nvrendererbackend.h"
#include <queue>
#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<const char*> stringSetToVector(
const std::unordered_set<std::string>& set) {
std::vector<const char*> ret;
for (const auto& s : set) {
ret.push_back(s.c_str());
}
return ret;
}
template <typename T>
std::vector<T> setToVector(const std::unordered_set<T>& set) {
std::vector<T> 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<const NvRendererBackend_Vulkan*>(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<NvRendererBackend_Vulkan>();
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<uint32_t>(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<uint64_t>::max(), // timeout
semaphore, vk::Fence(), &m_SwapChainIndex);
if (res == vk::Result::eErrorOutOfDateKHR) {
// BackBufferResizing();
auto surfaceCaps =
m_VulkanPhysicalDevice.getSurfaceCapabilitiesKHR(m_WindowSurface);
m_BackBufferWidth = static_cast<int>(surfaceCaps.currentExtent.width);
m_BackBufferHeight = static_cast<int>(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<PFN_vkGetInstanceProcAddr>(
"vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
return createInstance();
}
PFN_vkGetDeviceProcAddr NvRendererBackend_Vulkan::GetDeviceProcAddr() {
return m_dynamicLoader.getProcAddress<PFN_vkGetDeviceProcAddr>(
"vkGetDeviceProcAddr");
}
bool NvRendererBackend_Vulkan::CreateDevice() {
if (m_EnableDebugRuntime) {
installDebugCallback();
}
std::vector<std::string> 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<std::string> 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<std::string> 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<VkResult>(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<uint32_t>(layerVec.size()))
.setPpEnabledLayerNames(layerVec.data())
.setEnabledExtensionCount(
static_cast<uint32_t>(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<VkResult>(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<VkSurfaceKHR*>(&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<int>(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<vk::PhysicalDevice> discreteGPUs;
std::vector<vk::PhysicalDevice> 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<std::string> 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<vk::Format>(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<int>(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<int> 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<vk::DeviceQueueCreateInfo> 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<uint32_t>(queueDesc.size()))
.setPEnabledFeatures(&deviceFeatures)
.setEnabledExtensionCount(static_cast<uint32_t>(extVec.size()))
.setPpEnabledExtensionNames(extVec.data())
.setEnabledLayerCount(static_cast<uint32_t>(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<VkResult>(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<vk::Format>(nvrhi::vulkan::convertFormat(
nvrhi::Format::BGRA8_UNORM)),
vk::ColorSpaceKHR::eSrgbNonlinear};
vk::Extent2D extent = vk::Extent2D(m_BackBufferWidth, m_BackBufferHeight);
std::unordered_set<uint32_t> uniqueQueues = {
static_cast<uint32_t>(m_GraphicsQueueFamily),
static_cast<uint32_t>(m_PresentQueueFamily)};
std::vector<uint32_t> 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<vk::SwapchainCreateFlagBitsKHR>(0))
.setQueueFamilyIndexCount(
enableSwapChainSharing ? static_cast<uint32_t>(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<vk::Format> 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<VkResult>(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