mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 07:28:22 -10:00
This is basically the tutorial from vulkan-tutorial.com adapted to using VulkanHpp and with some refactorings that extract some locigally self-contained code. Currently only draws a simple colored triangle.
892 lines
30 KiB
C++
892 lines
30 KiB
C++
|
|
#include "VulkanRenderer.h"
|
|
|
|
#include "globalincs/version.h"
|
|
|
|
#include "def_files/def_files.h"
|
|
#include "graphics/2d.h"
|
|
#include "libs/renderdoc/renderdoc.h"
|
|
#include "mod_table/mod_table.h"
|
|
|
|
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
|
#include <SDL_vulkan.h>
|
|
#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
|
|
|
|
const SCP_vector<const char*> RequiredDeviceExtensions = {
|
|
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
|
};
|
|
|
|
bool checkDeviceExtensionSupport(PhysicalDeviceValues& values)
|
|
{
|
|
values.extensions = values.device.enumerateDeviceExtensionProperties();
|
|
|
|
std::set<std::string> requiredExtensions(RequiredDeviceExtensions.cbegin(), RequiredDeviceExtensions.cend());
|
|
for (const auto& extension : values.extensions) {
|
|
requiredExtensions.erase(extension.extensionName);
|
|
}
|
|
|
|
return requiredExtensions.empty();
|
|
}
|
|
|
|
bool checkSwapChainSupport(PhysicalDeviceValues& values, const vk::UniqueSurfaceKHR& surface)
|
|
{
|
|
values.surfaceCapabilities = values.device.getSurfaceCapabilitiesKHR(surface.get());
|
|
values.surfaceFormats = values.device.getSurfaceFormatsKHR(surface.get());
|
|
values.presentModes = values.device.getSurfacePresentModesKHR(surface.get());
|
|
|
|
return !values.surfaceFormats.empty() && !values.presentModes.empty();
|
|
}
|
|
|
|
bool isDeviceUnsuitable(PhysicalDeviceValues& values, const vk::UniqueSurfaceKHR& surface)
|
|
{
|
|
// We need a GPU. Reject CPU or "other" types.
|
|
if (values.properties.deviceType != vk::PhysicalDeviceType::eDiscreteGpu &&
|
|
values.properties.deviceType != vk::PhysicalDeviceType::eIntegratedGpu &&
|
|
values.properties.deviceType != vk::PhysicalDeviceType::eVirtualGpu) {
|
|
mprintf(("Rejecting %s (%d) because the device type is unsuitable.\n",
|
|
values.properties.deviceName,
|
|
values.properties.deviceID));
|
|
return true;
|
|
}
|
|
|
|
uint32_t i = 0;
|
|
for (const auto& queue : values.queueProperties) {
|
|
if (!values.graphicsQueueIndex.initialized && queue.queueFlags & vk::QueueFlagBits::eGraphics) {
|
|
values.graphicsQueueIndex.initialized = true;
|
|
values.graphicsQueueIndex.index = i;
|
|
}
|
|
if (!values.transferQueueIndex.initialized && queue.queueFlags & vk::QueueFlagBits::eTransfer) {
|
|
values.transferQueueIndex.initialized = true;
|
|
values.transferQueueIndex.index = i;
|
|
} else if (queue.queueFlags & vk::QueueFlagBits::eTransfer &&
|
|
!(queue.queueFlags & vk::QueueFlagBits::eGraphics)) {
|
|
// Found a dedicated transfer queue and we prefer that
|
|
values.transferQueueIndex.initialized = true;
|
|
values.transferQueueIndex.index = i;
|
|
}
|
|
if (!values.presentQueueIndex.initialized && values.device.getSurfaceSupportKHR(i, surface.get())) {
|
|
values.presentQueueIndex.initialized = true;
|
|
values.presentQueueIndex.index = i;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
|
|
if (!values.graphicsQueueIndex.initialized) {
|
|
mprintf(("Rejecting %s (%d) because the device does not have a graphics queue.\n",
|
|
values.properties.deviceName,
|
|
values.properties.deviceID));
|
|
return true;
|
|
}
|
|
if (!values.transferQueueIndex.initialized) {
|
|
mprintf(("Rejecting %s (%d) because the device does not have a transfer queue.\n",
|
|
values.properties.deviceName,
|
|
values.properties.deviceID));
|
|
return true;
|
|
}
|
|
if (!values.presentQueueIndex.initialized) {
|
|
mprintf(("Rejecting %s (%d) because the device does not have a presentation queue.\n",
|
|
values.properties.deviceName,
|
|
values.properties.deviceID));
|
|
return true;
|
|
}
|
|
|
|
if (!checkDeviceExtensionSupport(values)) {
|
|
mprintf(("Rejecting %s (%d) because the device does not support our required extensions.\n",
|
|
values.properties.deviceName,
|
|
values.properties.deviceID));
|
|
return true;
|
|
}
|
|
|
|
if (!checkSwapChainSupport(values, surface)) {
|
|
mprintf(("Rejecting %s (%d) because the device swap chain was not compatible.\n",
|
|
values.properties.deviceName,
|
|
values.properties.deviceID));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
int32_t deviceTypeScore(vk::PhysicalDeviceType type)
|
|
{
|
|
switch (type) {
|
|
case vk::PhysicalDeviceType::eIntegratedGpu:
|
|
return 1;
|
|
case vk::PhysicalDeviceType::eDiscreteGpu:
|
|
return 2;
|
|
case vk::PhysicalDeviceType::eVirtualGpu:
|
|
return 0;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int32_t scoreDevice(const PhysicalDeviceValues& device)
|
|
{
|
|
int32_t score = 0;
|
|
|
|
score += deviceTypeScore(device.properties.deviceType) * 1000;
|
|
|
|
return score;
|
|
}
|
|
|
|
bool compareDevices(const PhysicalDeviceValues& left, const PhysicalDeviceValues& right)
|
|
{
|
|
return scoreDevice(left) < scoreDevice(right);
|
|
}
|
|
|
|
void printPhysicalDevice(const PhysicalDeviceValues& values)
|
|
{
|
|
mprintf((" Found %s (%d) of type %s. API version %d.%d.%d, Driver version %d.%d.%d. Scored as %d\n",
|
|
values.properties.deviceName,
|
|
values.properties.deviceID,
|
|
to_string(values.properties.deviceType).c_str(),
|
|
VK_VERSION_MAJOR(values.properties.apiVersion),
|
|
VK_VERSION_MINOR(values.properties.apiVersion),
|
|
VK_VERSION_PATCH(values.properties.apiVersion),
|
|
VK_VERSION_MAJOR(values.properties.driverVersion),
|
|
VK_VERSION_MINOR(values.properties.driverVersion),
|
|
VK_VERSION_PATCH(values.properties.driverVersion),
|
|
scoreDevice(values)));
|
|
}
|
|
|
|
vk::SurfaceFormatKHR chooseSurfaceFormat(const PhysicalDeviceValues& values)
|
|
{
|
|
for (const auto& availableFormat : values.surfaceFormats) {
|
|
// Simple check is enough for now
|
|
if (availableFormat.format == vk::Format::eB8G8R8A8Srgb &&
|
|
availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
|
|
return availableFormat;
|
|
}
|
|
}
|
|
|
|
return values.surfaceFormats.front();
|
|
}
|
|
|
|
vk::PresentModeKHR choosePresentMode(const PhysicalDeviceValues& values)
|
|
{
|
|
// Depending on if we want Vsync or not, choose the best mode
|
|
for (const auto& availablePresentMode : values.presentModes) {
|
|
if (Gr_enable_vsync) {
|
|
if (availablePresentMode == vk::PresentModeKHR::eMailbox) {
|
|
return availablePresentMode;
|
|
}
|
|
} else {
|
|
if (availablePresentMode == vk::PresentModeKHR::eImmediate) {
|
|
return availablePresentMode;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Guaranteed to be supported
|
|
return vk::PresentModeKHR::eFifo;
|
|
}
|
|
|
|
vk::Extent2D chooseSwapChainExtent(const PhysicalDeviceValues& values, uint32_t width, uint32_t height)
|
|
{
|
|
if (values.surfaceCapabilities.currentExtent.width != UINT32_MAX) {
|
|
return values.surfaceCapabilities.currentExtent;
|
|
} else {
|
|
VkExtent2D actualExtent = {width, height};
|
|
|
|
actualExtent.width = std::max(values.surfaceCapabilities.minImageExtent.width,
|
|
std::min(values.surfaceCapabilities.maxImageExtent.width, actualExtent.width));
|
|
actualExtent.height = std::max(values.surfaceCapabilities.minImageExtent.height,
|
|
std::min(values.surfaceCapabilities.maxImageExtent.height, actualExtent.height));
|
|
|
|
return actualExtent;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
VulkanRenderer::VulkanRenderer(std::unique_ptr<os::GraphicsOperations> 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;
|
|
}
|
|
|
|
PhysicalDeviceValues deviceValues;
|
|
if (!pickPhysicalDevice(deviceValues)) {
|
|
mprintf(("Could not find suitable physical Vulkan device.\n"));
|
|
return false;
|
|
}
|
|
|
|
if (!createLogicalDevice(deviceValues)) {
|
|
mprintf(("Failed to create logical device.\n"));
|
|
return false;
|
|
}
|
|
|
|
if (!createSwapChain(deviceValues)) {
|
|
mprintf(("Failed to create swap chain.\n"));
|
|
return false;
|
|
}
|
|
|
|
createRenderPass();
|
|
createGraphicsPipeline();
|
|
createFrameBuffers();
|
|
createPresentSyncObjects();
|
|
createCommandPool(deviceValues);
|
|
|
|
// Prepare the rendering state by acquiring our first swap chain image
|
|
acquireNextSwapChainImage();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
auto viewPort = m_graphicsOps->createViewport(attrs);
|
|
if (!viewPort) {
|
|
return false;
|
|
}
|
|
|
|
auto port = os::addViewport(std::move(viewPort));
|
|
os::setMainViewPort(port);
|
|
|
|
return true;
|
|
}
|
|
bool VulkanRenderer::initializeInstance()
|
|
{
|
|
#if SDL_SUPPORTS_VULKAN
|
|
const auto vkGetInstanceProcAddr =
|
|
reinterpret_cast<PFN_vkGetInstanceProcAddr>(SDL_Vulkan_GetVkGetInstanceProcAddr());
|
|
|
|
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
|
|
|
|
const auto window = os::getSDLMainWindow();
|
|
|
|
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<const char*> 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<const char*> layers;
|
|
const auto supportedLayers = vk::enumerateInstanceLayerProperties();
|
|
mprintf(("Instance layers:\n"));
|
|
for (const auto& layer : supportedLayers) {
|
|
mprintf((" Found layer %s(%s). Spec version %d.%d.%d and implementation %" PRIu32 "\n",
|
|
layer.layerName,
|
|
layer.description,
|
|
VK_VERSION_MAJOR(layer.specVersion),
|
|
VK_VERSION_MINOR(layer.specVersion),
|
|
VK_VERSION_PATCH(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<vk::InstanceCreateFlagBits>(), &appInfo);
|
|
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
|
|
createInfo.ppEnabledExtensionNames = extensions.data();
|
|
createInfo.enabledLayerCount = static_cast<uint32_t>(layers.size());
|
|
createInfo.ppEnabledLayerNames = layers.data();
|
|
|
|
vk::DebugReportCallbackCreateInfoEXT createInstanceReportInfo(vk::DebugReportFlagBitsEXT::eError |
|
|
vk::DebugReportFlagBitsEXT::eWarning |
|
|
vk::DebugReportFlagBitsEXT::ePerformanceWarning);
|
|
createInstanceReportInfo.pfnCallback = debugReportCallback;
|
|
|
|
vk::StructureChain<vk::InstanceCreateInfo, vk::DebugReportCallbackCreateInfoEXT> createInstanceChain(createInfo,
|
|
createInstanceReportInfo);
|
|
|
|
if (!m_debugReportEnabled) {
|
|
createInstanceChain.unlink<vk::DebugReportCallbackCreateInfoEXT>();
|
|
}
|
|
|
|
vk::UniqueInstance instance = vk::createInstanceUnique(createInstanceChain.get<vk::InstanceCreateInfo>(), 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 = os::getSDLMainWindow();
|
|
|
|
VkSurfaceKHR surface;
|
|
if (!SDL_Vulkan_CreateSurface(window, static_cast<VkInstance>(*m_vkInstance), &surface)) {
|
|
mprintf(("Failed to create vulkan surface: %s\n", SDL_GetError()));
|
|
return false;
|
|
}
|
|
|
|
vk::ObjectDestroy<vk::Instance, VULKAN_HPP_DEFAULT_DISPATCHER_TYPE> deleter(*m_vkInstance,
|
|
nullptr,
|
|
VULKAN_HPP_DEFAULT_DISPATCHER);
|
|
m_vkSurface = vk::UniqueSurfaceKHR(vk::SurfaceKHR(surface), deleter);
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool VulkanRenderer::pickPhysicalDevice(PhysicalDeviceValues& deviceValues)
|
|
{
|
|
const auto devices = m_vkInstance->enumeratePhysicalDevices();
|
|
if (devices.empty()) {
|
|
return false;
|
|
}
|
|
|
|
SCP_vector<PhysicalDeviceValues> values;
|
|
std::transform(devices.cbegin(), devices.cend(), std::back_inserter(values), [](const vk::PhysicalDevice& dev) {
|
|
PhysicalDeviceValues vals;
|
|
vals.device = dev;
|
|
vals.properties = dev.getProperties2().properties;
|
|
vals.features = dev.getFeatures2().features;
|
|
vals.queueProperties = dev.getQueueFamilyProperties();
|
|
return vals;
|
|
});
|
|
|
|
mprintf(("Physical Vulkan devices:\n"));
|
|
std::for_each(values.cbegin(), values.cend(), printPhysicalDevice);
|
|
|
|
// Remove devices that do not have the features we need
|
|
values.erase(std::remove_if(values.begin(),
|
|
values.end(),
|
|
[this](PhysicalDeviceValues& value) { return isDeviceUnsuitable(value, m_vkSurface); }),
|
|
values.end());
|
|
if (values.empty()) {
|
|
return false;
|
|
}
|
|
|
|
// Sort the suitability of the devices in increasing order
|
|
std::sort(values.begin(), values.end(), compareDevices);
|
|
|
|
deviceValues = values.back();
|
|
mprintf(("Selected device %s (%d) as the primary Vulkan device.\n",
|
|
deviceValues.properties.deviceName,
|
|
deviceValues.properties.deviceID));
|
|
mprintf(("Device extensions:\n"));
|
|
for (const auto& extProp : deviceValues.extensions) {
|
|
mprintf((" Found support for %s version %" PRIu32 "\n", extProp.extensionName, extProp.specVersion));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VulkanRenderer::createLogicalDevice(PhysicalDeviceValues& deviceValues)
|
|
{
|
|
float queuePriority = 1.0f;
|
|
|
|
std::vector<vk::DeviceQueueCreateInfo> queueInfos;
|
|
std::set<uint32_t> familyIndices{deviceValues.graphicsQueueIndex.index,
|
|
deviceValues.transferQueueIndex.index,
|
|
deviceValues.presentQueueIndex.index};
|
|
|
|
queueInfos.reserve(familyIndices.size());
|
|
for (auto index : familyIndices) {
|
|
queueInfos.emplace_back(vk::DeviceQueueCreateFlags(), index, 1, &queuePriority);
|
|
}
|
|
|
|
vk::DeviceCreateInfo deviceCreate;
|
|
deviceCreate.pQueueCreateInfos = queueInfos.data();
|
|
deviceCreate.queueCreateInfoCount = static_cast<uint32_t>(queueInfos.size());
|
|
deviceCreate.pEnabledFeatures = &deviceValues.features;
|
|
|
|
deviceCreate.ppEnabledExtensionNames = RequiredDeviceExtensions.data();
|
|
deviceCreate.enabledExtensionCount = static_cast<uint32_t>(RequiredDeviceExtensions.size());
|
|
|
|
m_device = deviceValues.device.createDeviceUnique(deviceCreate);
|
|
|
|
// Create queues
|
|
m_graphicsQueue = m_device->getQueue(deviceValues.graphicsQueueIndex.index, 0);
|
|
m_transferQueue = m_device->getQueue(deviceValues.transferQueueIndex.index, 0);
|
|
m_presentQueue = m_device->getQueue(deviceValues.presentQueueIndex.index, 0);
|
|
|
|
return true;
|
|
}
|
|
bool VulkanRenderer::createSwapChain(PhysicalDeviceValues& deviceValues)
|
|
{
|
|
// Choose one more than the minimum to avoid driver synchronization if it is not done with a thread yet
|
|
uint32_t imageCount = deviceValues.surfaceCapabilities.minImageCount + 1;
|
|
if (deviceValues.surfaceCapabilities.maxImageCount > 0 &&
|
|
imageCount > deviceValues.surfaceCapabilities.maxImageCount) {
|
|
imageCount = deviceValues.surfaceCapabilities.maxImageCount;
|
|
}
|
|
|
|
const auto surfaceFormat = chooseSurfaceFormat(deviceValues);
|
|
|
|
vk::SwapchainCreateInfoKHR createInfo;
|
|
createInfo.surface = m_vkSurface.get();
|
|
createInfo.minImageCount = imageCount;
|
|
createInfo.imageFormat = surfaceFormat.format;
|
|
createInfo.imageColorSpace = surfaceFormat.colorSpace;
|
|
createInfo.imageExtent = chooseSwapChainExtent(deviceValues, gr_screen.max_w, gr_screen.max_h);
|
|
createInfo.imageArrayLayers = 1;
|
|
createInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment;
|
|
|
|
uint32_t queueFamilyIndices[] = {deviceValues.graphicsQueueIndex.index, deviceValues.presentQueueIndex.index};
|
|
if (deviceValues.graphicsQueueIndex.index != deviceValues.presentQueueIndex.index) {
|
|
createInfo.imageSharingMode = vk::SharingMode::eConcurrent;
|
|
createInfo.queueFamilyIndexCount = 2;
|
|
createInfo.pQueueFamilyIndices = queueFamilyIndices;
|
|
} else {
|
|
createInfo.imageSharingMode = vk::SharingMode::eExclusive;
|
|
createInfo.queueFamilyIndexCount = 0; // Optional
|
|
createInfo.pQueueFamilyIndices = nullptr; // Optional
|
|
}
|
|
|
|
createInfo.preTransform = deviceValues.surfaceCapabilities.currentTransform;
|
|
createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
|
createInfo.presentMode = choosePresentMode(deviceValues);
|
|
createInfo.clipped = true;
|
|
createInfo.oldSwapchain = nullptr;
|
|
|
|
m_swapChain = m_device->createSwapchainKHRUnique(createInfo);
|
|
|
|
m_swapChainImages = m_device->getSwapchainImagesKHR(m_swapChain.get());
|
|
m_swapChainImageFormat = surfaceFormat.format;
|
|
m_swapChainExtent = createInfo.imageExtent;
|
|
|
|
m_swapChainImageViews.reserve(m_swapChainImages.size());
|
|
for (const auto& image : m_swapChainImages) {
|
|
vk::ImageViewCreateInfo viewCreateInfo;
|
|
viewCreateInfo.image = image;
|
|
viewCreateInfo.viewType = vk::ImageViewType::e2D;
|
|
viewCreateInfo.format = m_swapChainImageFormat;
|
|
|
|
viewCreateInfo.components.r = vk::ComponentSwizzle::eIdentity;
|
|
viewCreateInfo.components.g = vk::ComponentSwizzle::eIdentity;
|
|
viewCreateInfo.components.b = vk::ComponentSwizzle::eIdentity;
|
|
viewCreateInfo.components.a = vk::ComponentSwizzle::eIdentity;
|
|
|
|
viewCreateInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
|
|
viewCreateInfo.subresourceRange.baseMipLevel = 0;
|
|
viewCreateInfo.subresourceRange.levelCount = 1;
|
|
viewCreateInfo.subresourceRange.baseArrayLayer = 0;
|
|
viewCreateInfo.subresourceRange.layerCount = 1;
|
|
|
|
m_swapChainImageViews.push_back(m_device->createImageViewUnique(viewCreateInfo));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
vk::UniqueShaderModule VulkanRenderer::loadShader(const SCP_string& name)
|
|
{
|
|
auto def_file = defaults_get_file(name.c_str());
|
|
|
|
vk::ShaderModuleCreateInfo createInfo;
|
|
createInfo.codeSize = def_file.size;
|
|
createInfo.pCode = reinterpret_cast<const uint32_t*>(def_file.data);
|
|
|
|
return m_device->createShaderModuleUnique(createInfo);
|
|
}
|
|
void VulkanRenderer::createFrameBuffers()
|
|
{
|
|
m_swapChainFramebuffers.reserve(m_swapChainImageViews.size());
|
|
for (const auto& imageView : m_swapChainImageViews) {
|
|
vk::ImageView attachments[] = {
|
|
imageView.get(),
|
|
};
|
|
|
|
vk::FramebufferCreateInfo framebufferInfo;
|
|
framebufferInfo.renderPass = m_renderPass.get();
|
|
framebufferInfo.attachmentCount = 1;
|
|
framebufferInfo.pAttachments = attachments;
|
|
framebufferInfo.width = m_swapChainExtent.width;
|
|
framebufferInfo.height = m_swapChainExtent.height;
|
|
framebufferInfo.layers = 1;
|
|
|
|
m_swapChainFramebuffers.push_back(m_device->createFramebufferUnique(framebufferInfo));
|
|
}
|
|
}
|
|
void VulkanRenderer::createRenderPass()
|
|
{
|
|
vk::AttachmentDescription colorAttachment;
|
|
colorAttachment.format = m_swapChainImageFormat;
|
|
colorAttachment.samples = vk::SampleCountFlagBits::e1;
|
|
|
|
colorAttachment.loadOp = vk::AttachmentLoadOp::eClear;
|
|
colorAttachment.storeOp = vk::AttachmentStoreOp::eStore;
|
|
|
|
colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
|
|
colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
|
|
|
|
colorAttachment.initialLayout = vk::ImageLayout::eUndefined;
|
|
colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR;
|
|
|
|
vk::AttachmentReference colorAttachRef;
|
|
colorAttachRef.attachment = 0;
|
|
colorAttachRef.layout = vk::ImageLayout::eColorAttachmentOptimal;
|
|
|
|
vk::SubpassDescription subpass;
|
|
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
|
|
subpass.colorAttachmentCount = 1;
|
|
subpass.pColorAttachments = &colorAttachRef;
|
|
|
|
vk::SubpassDependency dependency;
|
|
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
dependency.dstSubpass = 0;
|
|
|
|
dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
|
|
dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
|
dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
|
|
|
vk::RenderPassCreateInfo renderPassInfo;
|
|
renderPassInfo.attachmentCount = 1;
|
|
renderPassInfo.pAttachments = &colorAttachment;
|
|
renderPassInfo.subpassCount = 1;
|
|
renderPassInfo.pSubpasses = &subpass;
|
|
renderPassInfo.dependencyCount = 1;
|
|
renderPassInfo.pDependencies = &dependency;
|
|
|
|
m_renderPass = m_device->createRenderPassUnique(renderPassInfo);
|
|
}
|
|
void VulkanRenderer::createGraphicsPipeline()
|
|
{
|
|
auto vertShaderMod = loadShader("vulkan.vert.spv");
|
|
vk::PipelineShaderStageCreateInfo vertStageCreate;
|
|
vertStageCreate.stage = vk::ShaderStageFlagBits::eVertex;
|
|
vertStageCreate.module = vertShaderMod.get();
|
|
vertStageCreate.pName = "main";
|
|
|
|
auto fragShaderMod = loadShader("vulkan.frag.spv");
|
|
vk::PipelineShaderStageCreateInfo fragStageCreate;
|
|
fragStageCreate.stage = vk::ShaderStageFlagBits::eFragment;
|
|
fragStageCreate.module = fragShaderMod.get();
|
|
fragStageCreate.pName = "main";
|
|
|
|
std::array<vk::PipelineShaderStageCreateInfo, 2> shaderStages = {vertStageCreate, fragStageCreate};
|
|
|
|
vk::PipelineVertexInputStateCreateInfo vertInCreate;
|
|
vertInCreate.vertexBindingDescriptionCount = 0;
|
|
vertInCreate.vertexAttributeDescriptionCount = 0;
|
|
|
|
vk::PipelineInputAssemblyStateCreateInfo inputAssembly;
|
|
inputAssembly.topology = vk::PrimitiveTopology::eTriangleList;
|
|
inputAssembly.primitiveRestartEnable = false;
|
|
|
|
vk::Viewport viewport;
|
|
viewport.x = 0.0f;
|
|
viewport.y = 0.0f;
|
|
viewport.width = i2fl(gr_screen.max_w);
|
|
viewport.height = i2fl(gr_screen.max_h);
|
|
viewport.minDepth = 0.0f;
|
|
viewport.maxDepth = 1.0f;
|
|
|
|
vk::Rect2D scissor;
|
|
scissor.offset.x = 0;
|
|
scissor.offset.y = 0;
|
|
scissor.extent = m_swapChainExtent;
|
|
|
|
vk::PipelineViewportStateCreateInfo viewportState;
|
|
viewportState.viewportCount = 1;
|
|
viewportState.pViewports = &viewport;
|
|
viewportState.scissorCount = 1;
|
|
viewportState.pScissors = &scissor;
|
|
|
|
vk::PipelineRasterizationStateCreateInfo rasterizer;
|
|
rasterizer.depthClampEnable = false;
|
|
rasterizer.rasterizerDiscardEnable = false;
|
|
rasterizer.polygonMode = vk::PolygonMode::eFill;
|
|
rasterizer.lineWidth = 1.0f;
|
|
rasterizer.cullMode |= vk::CullModeFlagBits::eBack;
|
|
rasterizer.frontFace = vk::FrontFace::eClockwise;
|
|
rasterizer.depthBiasEnable = false;
|
|
rasterizer.depthBiasConstantFactor = 0.0f;
|
|
rasterizer.depthBiasClamp = 0.0f;
|
|
rasterizer.depthBiasSlopeFactor = 0.0f;
|
|
|
|
vk::PipelineMultisampleStateCreateInfo multisampling;
|
|
multisampling.sampleShadingEnable = false;
|
|
multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1;
|
|
multisampling.minSampleShading = 1.0f;
|
|
multisampling.pSampleMask = nullptr;
|
|
multisampling.alphaToCoverageEnable = false;
|
|
multisampling.alphaToOneEnable = false;
|
|
|
|
vk::PipelineColorBlendAttachmentState colorBlendAttachment;
|
|
colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
|
colorBlendAttachment.blendEnable = false;
|
|
colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eOne; // Optional
|
|
colorBlendAttachment.dstColorBlendFactor = vk::BlendFactor::eZero; // Optional
|
|
colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd; // Optional
|
|
colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne; // Optional
|
|
colorBlendAttachment.dstAlphaBlendFactor = vk::BlendFactor::eZero; // Optional
|
|
colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; // Optional
|
|
|
|
vk::PipelineColorBlendStateCreateInfo colorBlending;
|
|
colorBlending.logicOpEnable = false;
|
|
colorBlending.logicOp = vk::LogicOp::eCopy;
|
|
colorBlending.attachmentCount = 1;
|
|
colorBlending.pAttachments = &colorBlendAttachment;
|
|
colorBlending.blendConstants[0] = 0.0f;
|
|
colorBlending.blendConstants[1] = 0.0f;
|
|
colorBlending.blendConstants[2] = 0.0f;
|
|
colorBlending.blendConstants[3] = 0.0f;
|
|
|
|
vk::DynamicState dynamicStates[] = {
|
|
vk::DynamicState::eViewport,
|
|
vk::DynamicState::eLineWidth,
|
|
};
|
|
|
|
vk::PipelineDynamicStateCreateInfo dynamicStateInfo;
|
|
dynamicStateInfo.dynamicStateCount = 2;
|
|
dynamicStateInfo.pDynamicStates = dynamicStates;
|
|
|
|
vk::PipelineLayoutCreateInfo pipelineLayout;
|
|
pipelineLayout.setLayoutCount = 0;
|
|
pipelineLayout.pSetLayouts = nullptr;
|
|
pipelineLayout.pushConstantRangeCount = 0;
|
|
pipelineLayout.pPushConstantRanges = nullptr;
|
|
|
|
m_pipelineLayout = m_device->createPipelineLayoutUnique(pipelineLayout);
|
|
|
|
vk::GraphicsPipelineCreateInfo pipelineInfo;
|
|
pipelineInfo.stageCount = 2;
|
|
pipelineInfo.pStages = shaderStages.data();
|
|
pipelineInfo.pVertexInputState = &vertInCreate;
|
|
pipelineInfo.pInputAssemblyState = &inputAssembly;
|
|
pipelineInfo.pViewportState = &viewportState;
|
|
pipelineInfo.pRasterizationState = &rasterizer;
|
|
pipelineInfo.pMultisampleState = &multisampling;
|
|
pipelineInfo.pDepthStencilState = nullptr;
|
|
pipelineInfo.pColorBlendState = &colorBlending;
|
|
pipelineInfo.pDynamicState = nullptr;
|
|
pipelineInfo.layout = m_pipelineLayout.get();
|
|
pipelineInfo.renderPass = m_renderPass.get();
|
|
pipelineInfo.subpass = 0;
|
|
pipelineInfo.basePipelineHandle = nullptr;
|
|
pipelineInfo.basePipelineIndex = -1;
|
|
|
|
m_graphicsPipeline = m_device->createGraphicsPipelineUnique(nullptr, pipelineInfo);
|
|
}
|
|
void VulkanRenderer::createCommandPool(const PhysicalDeviceValues& values)
|
|
{
|
|
vk::CommandPoolCreateInfo poolCreate;
|
|
poolCreate.queueFamilyIndex = values.graphicsQueueIndex.index;
|
|
poolCreate.flags |= vk::CommandPoolCreateFlagBits::eTransient;
|
|
|
|
m_graphicsCommandPool = m_device->createCommandPoolUnique(poolCreate);
|
|
}
|
|
void VulkanRenderer::createPresentSyncObjects()
|
|
{
|
|
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
|
|
m_frames[i].reset(new RenderFrame(m_device.get(), m_swapChain.get(), m_graphicsQueue, m_presentQueue));
|
|
}
|
|
|
|
m_swapChainImageRenderImage.resize(m_swapChainImages.size(), nullptr);
|
|
}
|
|
void VulkanRenderer::acquireNextSwapChainImage()
|
|
{
|
|
m_frames[m_currentFrame]->waitForFinish();
|
|
|
|
m_currentSwapChainImage = m_frames[m_currentFrame]->acquireSwapchainImage();
|
|
|
|
// Ensure that this image is no longer in use
|
|
if (m_swapChainImageRenderImage[m_currentSwapChainImage]) {
|
|
m_swapChainImageRenderImage[m_currentSwapChainImage]->waitForFinish();
|
|
}
|
|
// Reserve the image as in use
|
|
m_swapChainImageRenderImage[m_currentSwapChainImage] = m_frames[m_currentFrame].get();
|
|
}
|
|
void VulkanRenderer::drawScene(vk::Framebuffer destinationFb, vk::CommandBuffer cmdBuffer)
|
|
{
|
|
vk::CommandBufferBeginInfo beginInfo;
|
|
beginInfo.flags |= vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
|
|
|
|
cmdBuffer.begin(beginInfo);
|
|
|
|
vk::RenderPassBeginInfo renderPassBegin;
|
|
renderPassBegin.renderPass = m_renderPass.get();
|
|
renderPassBegin.framebuffer = destinationFb;
|
|
renderPassBegin.renderArea.offset.x = 0;
|
|
renderPassBegin.renderArea.offset.y = 0;
|
|
renderPassBegin.renderArea.extent = m_swapChainExtent;
|
|
|
|
vk::ClearValue clearColor;
|
|
clearColor.color.setFloat32({0.0f, 0.0f, 0.0f, 1.0f});
|
|
|
|
renderPassBegin.clearValueCount = 1;
|
|
renderPassBegin.pClearValues = &clearColor;
|
|
|
|
cmdBuffer.beginRenderPass(renderPassBegin, vk::SubpassContents::eInline);
|
|
|
|
cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, m_graphicsPipeline.get());
|
|
|
|
cmdBuffer.draw(3, 1, 0, 0);
|
|
|
|
cmdBuffer.endRenderPass();
|
|
|
|
cmdBuffer.end();
|
|
}
|
|
void VulkanRenderer::flip()
|
|
{
|
|
vk::CommandBufferAllocateInfo cmdBufferAlloc;
|
|
cmdBufferAlloc.commandPool = m_graphicsCommandPool.get();
|
|
cmdBufferAlloc.level = vk::CommandBufferLevel::ePrimary;
|
|
cmdBufferAlloc.commandBufferCount = 1;
|
|
|
|
// Uses the non-unique version since we can't get the buffers into the lambda below otherwise. Only C++14 can do
|
|
// that
|
|
auto allocatedBuffers = m_device->allocateCommandBuffers(cmdBufferAlloc);
|
|
auto& cmdBuffer = allocatedBuffers.front();
|
|
|
|
drawScene(m_swapChainFramebuffers[m_currentSwapChainImage].get(), cmdBuffer);
|
|
m_frames[m_currentFrame]->onFrameFinished([this, allocatedBuffers]() mutable {
|
|
m_device->freeCommandBuffers(m_graphicsCommandPool.get(), allocatedBuffers);
|
|
allocatedBuffers.clear();
|
|
});
|
|
|
|
m_frames[m_currentFrame]->submitAndPresent(allocatedBuffers);
|
|
|
|
// Advance counters to prepare for the next frame
|
|
m_currentFrame = (m_currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
|
|
|
|
acquireNextSwapChainImage();
|
|
}
|
|
void VulkanRenderer::shutdown()
|
|
{
|
|
// Wait for all frames to complete to ensure no drawing is in progress when we destroy the device
|
|
for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
|
|
m_frames[i]->waitForFinish();
|
|
}
|
|
// For good measure, also wait until the device is idle
|
|
m_device->waitIdle();
|
|
}
|
|
|
|
} // namespace vulkan
|
|
} // namespace graphics
|