#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 #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 RequiredDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; bool checkDeviceExtensionSupport(PhysicalDeviceValues& values) { values.extensions = values.device.enumerateDeviceExtensionProperties(); std::set 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 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(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 extensions; extensions.resize(count); if (!SDL_Vulkan_GetInstanceExtensions(window, &count, extensions.data())) { mprintf(("Error in second SDL_Vulkan_GetInstanceExtensions: %s\n", SDL_GetError())); return false; } const auto instanceVersion = vk::enumerateInstanceVersion(); gameversion::version vulkanVersion(VK_VERSION_MAJOR(instanceVersion), VK_VERSION_MINOR(instanceVersion), VK_VERSION_PATCH(instanceVersion), 0); mprintf(("Vulkan instance version %s\n", gameversion::format_version(vulkanVersion).c_str())); if (vulkanVersion < MinVulkanVersion) { mprintf(("Vulkan version is less than the minimum which is %s.\n", gameversion::format_version(MinVulkanVersion).c_str())); return false; } const auto supportedExtensions = vk::enumerateInstanceExtensionProperties(); mprintf(("Instance extensions:\n")); for (const auto& ext : supportedExtensions) { mprintf((" Found support for %s version %" PRIu32 "\n", ext.extensionName, ext.specVersion)); if (FSO_DEBUG || Cmdline_graphics_debug_output) { if (!stricmp(ext.extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) { extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); m_debugReportEnabled = true; } } } std::vector layers; const auto supportedLayers = vk::enumerateInstanceLayerProperties(); mprintf(("Instance layers:\n")); for (const auto& layer : supportedLayers) { mprintf((" Found layer %s(%s). Spec version %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(), &appInfo); createInfo.enabledExtensionCount = static_cast(extensions.size()); createInfo.ppEnabledExtensionNames = extensions.data(); createInfo.enabledLayerCount = static_cast(layers.size()); createInfo.ppEnabledLayerNames = layers.data(); vk::DebugReportCallbackCreateInfoEXT createInstanceReportInfo(vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning); createInstanceReportInfo.pfnCallback = debugReportCallback; vk::StructureChain createInstanceChain(createInfo, createInstanceReportInfo); if (!m_debugReportEnabled) { createInstanceChain.unlink(); } vk::UniqueInstance instance = vk::createInstanceUnique(createInstanceChain.get(), nullptr); if (!instance) { return false; } VULKAN_HPP_DEFAULT_DISPATCHER.init(instance.get()); if (m_debugReportEnabled) { vk::DebugReportCallbackCreateInfoEXT reportCreateInfo(vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning); reportCreateInfo.pfnCallback = debugReportCallback; m_debugReport = instance->createDebugReportCallbackEXTUnique(reportCreateInfo); } m_vkInstance = std::move(instance); return true; #else mprintf(("SDL does not support Vulkan in its current version.\n")); return false; #endif } bool VulkanRenderer::initializeSurface() { #if SDL_SUPPORTS_VULKAN const auto window = os::getSDLMainWindow(); VkSurfaceKHR surface; if (!SDL_Vulkan_CreateSurface(window, static_cast(*m_vkInstance), &surface)) { mprintf(("Failed to create vulkan surface: %s\n", SDL_GetError())); return false; } vk::ObjectDestroy deleter(*m_vkInstance, nullptr, VULKAN_HPP_DEFAULT_DISPATCHER); m_vkSurface = vk::UniqueSurfaceKHR(vk::SurfaceKHR(surface), deleter); return true; #else return false; #endif } bool VulkanRenderer::pickPhysicalDevice(PhysicalDeviceValues& deviceValues) { const auto devices = m_vkInstance->enumeratePhysicalDevices(); if (devices.empty()) { return false; } SCP_vector 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 queueInfos; std::set 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(queueInfos.size()); deviceCreate.pEnabledFeatures = &deviceValues.features; deviceCreate.ppEnabledExtensionNames = RequiredDeviceExtensions.data(); deviceCreate.enabledExtensionCount = static_cast(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(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 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