/* * Class wrapping access to the swap chain * * Copyright (C) 2016 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #pragma once #include #include #include #include #include #ifdef _WIN32 #include #include #include #else #endif #include #include "vulkantools.h" #ifdef __ANDROID__ #include "vulkanandroid.h" #endif // Macro to get a procedure address based on a vulkan instance #define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \ { \ fp##entrypoint = (PFN_vk##entrypoint) vkGetInstanceProcAddr(inst, "vk"#entrypoint); \ if (fp##entrypoint == NULL) \ { \ exit(1); \ } \ } // Macro to get a procedure address based on a vulkan device #define GET_DEVICE_PROC_ADDR(dev, entrypoint) \ { \ fp##entrypoint = (PFN_vk##entrypoint) vkGetDeviceProcAddr(dev, "vk"#entrypoint); \ if (fp##entrypoint == NULL) \ { \ exit(1); \ } \ } typedef struct _SwapChainBuffers { VkImage image; VkImageView view; } SwapChainBuffer; class VulkanSwapChain { private: VkInstance instance; VkDevice device; VkPhysicalDevice physicalDevice; VkSurfaceKHR surface; // Function pointers PFN_vkGetPhysicalDeviceSurfaceSupportKHR fpGetPhysicalDeviceSurfaceSupportKHR; PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR fpGetPhysicalDeviceSurfaceCapabilitiesKHR; PFN_vkGetPhysicalDeviceSurfaceFormatsKHR fpGetPhysicalDeviceSurfaceFormatsKHR; PFN_vkGetPhysicalDeviceSurfacePresentModesKHR fpGetPhysicalDeviceSurfacePresentModesKHR; PFN_vkCreateSwapchainKHR fpCreateSwapchainKHR; PFN_vkDestroySwapchainKHR fpDestroySwapchainKHR; PFN_vkGetSwapchainImagesKHR fpGetSwapchainImagesKHR; PFN_vkAcquireNextImageKHR fpAcquireNextImageKHR; PFN_vkQueuePresentKHR fpQueuePresentKHR; public: VkFormat colorFormat; VkColorSpaceKHR colorSpace; VkImage* swapchainImages; VkSwapchainKHR swapChain = VK_NULL_HANDLE; uint32_t imageCount; SwapChainBuffer* buffers; // Index of the deteced graphics and presenting device queue uint32_t queueNodeIndex = UINT32_MAX; // wip naming void initSwapChain( #ifdef _WIN32 void* platformHandle, void* platformWindow #else #ifdef __ANDROID__ ANativeWindow* window #else xcb_connection_t* connection, xcb_window_t window #endif #endif ) { uint32_t queueCount; VkQueueFamilyProperties *queueProps; VkResult err; // Create surface depending on OS #ifdef _WIN32 VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; surfaceCreateInfo.hinstance = (HINSTANCE)platformHandle; surfaceCreateInfo.hwnd = (HWND)platformWindow; err = vkCreateWin32SurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface); #else #ifdef __ANDROID__ VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR; surfaceCreateInfo.window = window; err = vkCreateAndroidSurfaceKHR(instance, &surfaceCreateInfo, NULL, &surface); #else VkXcbSurfaceCreateInfoKHR surfaceCreateInfo = {}; surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR; surfaceCreateInfo.connection = connection; surfaceCreateInfo.window = window; err = vkCreateXcbSurfaceKHR(instance, &surfaceCreateInfo, nullptr, &surface); #endif #endif uint32_t i; // Get queue properties vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL); queueProps = (VkQueueFamilyProperties *)malloc(queueCount * sizeof(VkQueueFamilyProperties)); vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, queueProps); assert(queueCount >= 1); // Iterate over each queue to learn whether it supports presenting: VkBool32* supportsPresent = (VkBool32 *)malloc(queueCount * sizeof(VkBool32)); for (i = 0; i < queueCount; i++) { fpGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &supportsPresent[i]); } // Search for a graphics and a present queue in the array of queue // families, try to find one that supports both uint32_t graphicsQueueNodeIndex = UINT32_MAX; uint32_t presentQueueNodeIndex = UINT32_MAX; for (i = 0; i < queueCount; i++) { if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { if (graphicsQueueNodeIndex == UINT32_MAX) { graphicsQueueNodeIndex = i; } if (supportsPresent[i] == VK_TRUE) { graphicsQueueNodeIndex = i; presentQueueNodeIndex = i; break; } } } if (presentQueueNodeIndex == UINT32_MAX) { // If there's no queue that supports both present and graphics // try to find a separate present queue for (uint32_t i = 0; i < queueCount; ++i) { if (supportsPresent[i] == VK_TRUE) { presentQueueNodeIndex = i; break; } } } free(supportsPresent); // Generate error if could not find both a graphics and a present queue if (graphicsQueueNodeIndex == UINT32_MAX || presentQueueNodeIndex == UINT32_MAX) { // todo : error message } if (graphicsQueueNodeIndex != presentQueueNodeIndex) { // todo : error message } queueNodeIndex = graphicsQueueNodeIndex; // Get list of supported formats uint32_t formatCount; err = fpGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL); assert(!err); VkSurfaceFormatKHR *surfFormats = (VkSurfaceFormatKHR *)malloc(formatCount * sizeof(VkSurfaceFormatKHR)); err = fpGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, surfFormats); assert(!err); // If the format list includes just one entry of VK_FORMAT_UNDEFINED, // the surface has no preferred format. Otherwise, at least one // supported format will be returned. if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED) { colorFormat = VK_FORMAT_B8G8R8A8_UNORM; } else { assert(formatCount >= 1); colorFormat = surfFormats[0].format; } colorSpace = surfFormats[0].colorSpace; } void init(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device) { this->instance = instance; this->physicalDevice = physicalDevice; this->device = device; GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceSupportKHR); GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceCapabilitiesKHR); GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceFormatsKHR); GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfacePresentModesKHR); GET_DEVICE_PROC_ADDR(device, CreateSwapchainKHR); GET_DEVICE_PROC_ADDR(device, DestroySwapchainKHR); GET_DEVICE_PROC_ADDR(device, GetSwapchainImagesKHR); GET_DEVICE_PROC_ADDR(device, AcquireNextImageKHR); GET_DEVICE_PROC_ADDR(device, QueuePresentKHR); } void setup(VkCommandBuffer cmdBuffer, uint32_t *width, uint32_t *height) { VkResult err; VkSwapchainKHR oldSwapchain = swapChain; // Get physical device surface properties and formats VkSurfaceCapabilitiesKHR surfCaps; err = fpGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &surfCaps); assert(!err); uint32_t presentModeCount; err = fpGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, NULL); assert(!err); // todo : replace with vector? VkPresentModeKHR *presentModes = (VkPresentModeKHR *)malloc(presentModeCount * sizeof(VkPresentModeKHR)); err = fpGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes); assert(!err); VkExtent2D swapchainExtent = {}; // width and height are either both -1, or both not -1. if (surfCaps.currentExtent.width == -1) { // If the surface size is undefined, the size is set to // the size of the images requested. swapchainExtent.width = *width; swapchainExtent.height = *height; } else { // If the surface size is defined, the swap chain size must match swapchainExtent = surfCaps.currentExtent; *width = surfCaps.currentExtent.width; *height = surfCaps.currentExtent.height; } // Try to use mailbox mode // Low latency and non-tearing VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; for (size_t i = 0; i < presentModeCount; i++) { if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR; break; } if ((swapchainPresentMode != VK_PRESENT_MODE_MAILBOX_KHR) && (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)) { swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; } } // Determine the number of images uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1; if ((surfCaps.maxImageCount > 0) && (desiredNumberOfSwapchainImages > surfCaps.maxImageCount)) { desiredNumberOfSwapchainImages = surfCaps.maxImageCount; } VkSurfaceTransformFlagsKHR preTransform; if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; } else { preTransform = surfCaps.currentTransform; } VkSwapchainCreateInfoKHR swapchainCI = {}; swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchainCI.pNext = NULL; swapchainCI.surface = surface; swapchainCI.minImageCount = desiredNumberOfSwapchainImages; swapchainCI.imageFormat = colorFormat; swapchainCI.imageColorSpace = colorSpace; swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height }; swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform; swapchainCI.imageArrayLayers = 1; swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchainCI.queueFamilyIndexCount = 0; swapchainCI.pQueueFamilyIndices = NULL; swapchainCI.presentMode = swapchainPresentMode; swapchainCI.oldSwapchain = oldSwapchain; swapchainCI.clipped = true; swapchainCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; err = fpCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapChain); assert(!err); // If we just re-created an existing swapchain, we should destroy the old // swapchain at this point. // Note: destroying the swapchain also cleans up all its associated // presentable images once the platform is done with them. if (oldSwapchain != VK_NULL_HANDLE) { fpDestroySwapchainKHR(device, oldSwapchain, nullptr); } err = fpGetSwapchainImagesKHR(device, swapChain, &imageCount, NULL); assert(!err); swapchainImages = (VkImage*)malloc(imageCount * sizeof(VkImage)); assert(swapchainImages); err = fpGetSwapchainImagesKHR(device, swapChain, &imageCount, swapchainImages); assert(!err); buffers = (SwapChainBuffer*)malloc(sizeof(SwapChainBuffer)*imageCount); assert(buffers); //buffers.resize(imageCount); for (uint32_t i = 0; i < imageCount; i++) { VkImageViewCreateInfo colorAttachmentView = {}; colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; colorAttachmentView.pNext = NULL; colorAttachmentView.format = colorFormat; colorAttachmentView.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; colorAttachmentView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; colorAttachmentView.subresourceRange.baseMipLevel = 0; colorAttachmentView.subresourceRange.levelCount = 1; colorAttachmentView.subresourceRange.baseArrayLayer = 0; colorAttachmentView.subresourceRange.layerCount = 1; colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D; colorAttachmentView.flags = 0; buffers[i].image = swapchainImages[i]; vkTools::setImageLayout( cmdBuffer, buffers[i].image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); colorAttachmentView.image = buffers[i].image; err = vkCreateImageView(device, &colorAttachmentView, nullptr, &buffers[i].view); assert(!err); } } // Acquires the next image in the swap chain VkResult acquireNextImage(VkSemaphore presentCompleteSemaphore, uint32_t *currentBuffer) { return fpAcquireNextImageKHR(device, swapChain, UINT64_MAX, presentCompleteSemaphore, (VkFence)nullptr, currentBuffer); } // Present the current image to the queue VkResult queuePresent(VkQueue queue, uint32_t currentBuffer) { VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.pNext = NULL; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = &swapChain; presentInfo.pImageIndices = ¤tBuffer; return fpQueuePresentKHR(queue, &presentInfo); } void cleanup() { for (uint32_t i = 0; i < imageCount; i++) { vkDestroyImageView(device, buffers[i].view, nullptr); } fpDestroySwapchainKHR(device, swapChain, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); } };