Created
March 12, 2021 01:53
-
-
Save RoryO/1dec504cab731a9eef5f4f9899c2644d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// build with | |
// cc -g nvidia-swapchain-hang.cpp -lm -lxcb -lvulkan | |
#ifdef _WIN32 | |
#define VK_USE_PLATFORM_WIN32_KHR | |
#include <windows.h> | |
#elif __linux__ | |
#define VK_USE_PLATFORM_XCB_KHR | |
#include <xcb/xcb.h> | |
#endif | |
#include "vulkan.h" | |
#include <stdlib.h> | |
#include <stdio.h> | |
struct VulkanState { | |
VkDevice device; | |
VkSwapchainKHR swapchain; | |
VkSurfaceCapabilitiesKHR surface_capabilities; | |
uint32_t swapchain_image_count; | |
VkImage swapchain_images[2]; | |
VkSurfaceKHR surface; | |
VkPhysicalDevice selected_physical_device; | |
VkCommandBuffer setup_command_buffer; | |
VkQueue queue; | |
} g_vulkanState; | |
void | |
create_swapchain() { | |
printf("creating swapchain\n"); | |
VkSwapchainKHR new_swapchain = {}; | |
uint32_t n_surface_formats = 0; | |
vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkanState.selected_physical_device, | |
g_vulkanState.surface, &n_surface_formats, nullptr); | |
VkSurfaceFormatKHR *surface_formats = reinterpret_cast<VkSurfaceFormatKHR*>(malloc(sizeof(VkSurfaceFormatKHR) * n_surface_formats)); | |
vkGetPhysicalDeviceSurfaceFormatsKHR(g_vulkanState.selected_physical_device, | |
g_vulkanState.surface, &n_surface_formats, surface_formats); | |
uint32_t selected_surface_format = 0; | |
for(uint32_t i = 0; i < n_surface_formats; ++i) { | |
if(surface_formats[i].format == VK_FORMAT_B8G8R8A8_SRGB) { | |
selected_surface_format = i; | |
break; | |
} | |
} | |
VkSurfaceFormatKHR surface_format = surface_formats[selected_surface_format]; | |
free(surface_formats); | |
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(g_vulkanState.selected_physical_device, | |
g_vulkanState.surface, &g_vulkanState.surface_capabilities); | |
VkSwapchainCreateInfoKHR swapchain_create_info = {}; | |
swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; | |
swapchain_create_info.surface = g_vulkanState.surface; | |
swapchain_create_info.minImageCount = 2; | |
swapchain_create_info.imageFormat = surface_format.format; | |
swapchain_create_info.imageColorSpace = surface_format.colorSpace; | |
swapchain_create_info.imageExtent.width = g_vulkanState.surface_capabilities.currentExtent.width; | |
swapchain_create_info.imageExtent.height = g_vulkanState.surface_capabilities.currentExtent.height; | |
swapchain_create_info.imageArrayLayers = 1; | |
swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; | |
swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
swapchain_create_info.preTransform = g_vulkanState.surface_capabilities.currentTransform; | |
swapchain_create_info.compositeAlpha = static_cast<VkCompositeAlphaFlagBitsKHR> | |
(g_vulkanState.surface_capabilities.supportedCompositeAlpha); | |
swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; | |
swapchain_create_info.clipped = VK_TRUE; | |
// this triggers the hang at vkAcquireNextImageKHR when g_vulkanState.swapchain | |
// is a valid handle to an existing swapchain on Linux | |
// comment, resize the window and the program works fine | |
// this does not cause an issue on Windows | |
swapchain_create_info.oldSwapchain = g_vulkanState.swapchain; | |
vkCreateSwapchainKHR(g_vulkanState.device, | |
&swapchain_create_info, | |
nullptr, | |
&new_swapchain); | |
if(g_vulkanState.swapchain != VK_NULL_HANDLE) { | |
vkDestroySwapchainKHR(g_vulkanState.device, g_vulkanState.swapchain, nullptr); | |
} | |
g_vulkanState.swapchain = new_swapchain; | |
vkGetSwapchainImagesKHR(g_vulkanState.device, g_vulkanState.swapchain, | |
&g_vulkanState.swapchain_image_count, nullptr); | |
vkGetSwapchainImagesKHR(g_vulkanState.device, g_vulkanState.swapchain, | |
&g_vulkanState.swapchain_image_count, | |
g_vulkanState.swapchain_images); | |
VkCommandBufferBeginInfo transition_begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO }; | |
transition_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | |
vkBeginCommandBuffer(g_vulkanState.setup_command_buffer, &transition_begin_info); | |
VkImageMemoryBarrier transition_barriers[2] = {}; | |
for(uint32_t i = 0; i < 2; ++i) { | |
transition_barriers[i].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; | |
transition_barriers[i].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
transition_barriers[i].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
transition_barriers[i].image = g_vulkanState.swapchain_images[i]; | |
transition_barriers[i].subresourceRange.baseMipLevel = 0; | |
transition_barriers[i].subresourceRange.levelCount = 1; | |
transition_barriers[i].subresourceRange.baseArrayLayer = 0; | |
transition_barriers[i].subresourceRange.layerCount = 1; | |
transition_barriers[i].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
} | |
vkCmdPipelineBarrier(g_vulkanState.setup_command_buffer, | |
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, | |
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, | |
0, | |
0, nullptr, | |
0, nullptr, | |
2, transition_barriers); | |
vkEndCommandBuffer(g_vulkanState.setup_command_buffer); | |
VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; | |
submit_info.commandBufferCount = 1; | |
submit_info.pCommandBuffers = &g_vulkanState.setup_command_buffer; | |
vkQueueSubmit(g_vulkanState.queue, 1, &submit_info, nullptr); | |
vkQueueWaitIdle(g_vulkanState.queue); | |
vkResetCommandBuffer(g_vulkanState.setup_command_buffer, 0); | |
} | |
int | |
main() { | |
xcb_connection_t *xcb_connection = xcb_connect(nullptr, nullptr); | |
xcb_window_t xcb_window = xcb_generate_id(xcb_connection); | |
xcb_atom_t wm_delete_window = 0; | |
{ | |
xcb_screen_t *xcb_screen = xcb_setup_roots_iterator(xcb_get_setup(xcb_connection)).data; | |
uint32_t values[2]; | |
values[0] = xcb_screen->black_pixel; | |
values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | | |
XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | | |
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | | |
XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_MOTION; | |
xcb_create_window(xcb_connection, | |
XCB_COPY_FROM_PARENT, | |
xcb_window, | |
xcb_screen->root, | |
0, 0, | |
1024, 768, | |
10, | |
XCB_WINDOW_CLASS_INPUT_OUTPUT, | |
xcb_screen->root_visual, | |
XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK, | |
values); | |
xcb_map_window(xcb_connection, xcb_window); | |
xcb_flush(xcb_connection); | |
xcb_generic_error_t *xcb_err = nullptr; | |
xcb_intern_atom_reply_t *atom_reply; | |
atom_reply = xcb_intern_atom_reply(xcb_connection, | |
xcb_intern_atom(xcb_connection, 1, 12, "WM_PROTOCOLS"), | |
&xcb_err); | |
xcb_atom_t wm_protocols = atom_reply->atom; | |
free(atom_reply); | |
atom_reply = xcb_intern_atom_reply(xcb_connection, | |
xcb_intern_atom(xcb_connection, 0, 16, "WM_DELETE_WINDOW"), | |
&xcb_err); | |
wm_delete_window = atom_reply->atom; | |
free(atom_reply); | |
xcb_discard_reply(xcb_connection, | |
xcb_change_property(xcb_connection, XCB_PROP_MODE_REPLACE, xcb_window, | |
wm_protocols, 4, 32, 1, &wm_delete_window).sequence); | |
if(xcb_err) { | |
free(xcb_err); | |
} | |
} | |
// create instance | |
VkInstance instance = {}; | |
{ | |
const char *enabled_layer_names[] = { | |
"VK_LAYER_KHRONOS_validation", | |
}; | |
uint32_t enabled_layer_count = sizeof(enabled_layer_names) / | |
sizeof(enabled_layer_names[0]); | |
const char *enabled_instance_extension_names[] = { | |
"VK_KHR_surface", | |
#ifdef _WIN32 | |
"VK_KHR_win32_surface", | |
#elif __linux__ | |
"VK_KHR_xcb_surface", | |
#else | |
#endif | |
}; | |
uint32_t enabled_instance_extension_count = sizeof(enabled_instance_extension_names) / | |
sizeof(enabled_instance_extension_names[0]); | |
VkApplicationInfo application_info = {}; | |
application_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | |
application_info.apiVersion = VK_MAKE_VERSION(1, 2, 0); | |
VkInstanceCreateInfo create_info = {}; | |
create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | |
create_info.pApplicationInfo = &application_info; | |
create_info.ppEnabledLayerNames = enabled_layer_names; | |
create_info.enabledLayerCount = enabled_layer_count; | |
create_info.ppEnabledExtensionNames = enabled_instance_extension_names; | |
create_info.enabledExtensionCount = enabled_instance_extension_count; | |
vkCreateInstance(&create_info, nullptr, &instance); | |
} | |
// create surface | |
{ | |
#ifdef _WIN32 | |
VkWin32SurfaceCreateInfoKHR surface_create_info = {}; | |
surface_create_info.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; | |
surface_create_info.hwnd = main_window; | |
surface_create_info.hinstance = process_instance; | |
vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &g_vulkanState.surface); | |
#elif __linux__ | |
VkXcbSurfaceCreateInfoKHR surface_create_info = { VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR }; | |
surface_create_info.connection = xcb_connection; | |
surface_create_info.window = xcb_window; | |
vkCreateXcbSurfaceKHR(instance, &surface_create_info, nullptr, &g_vulkanState.surface); | |
#endif | |
} | |
// select physical device | |
const uint8_t MAX_PHYSICAL_DEVICES = 5; | |
VkPhysicalDevice physical_devices[MAX_PHYSICAL_DEVICES] = {}; | |
{ | |
uint32_t physical_device_count = MAX_PHYSICAL_DEVICES; | |
vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices); | |
if(physical_device_count == 1) { | |
g_vulkanState.selected_physical_device = physical_devices[0]; | |
} | |
else { | |
bool found_discrete = false; | |
for(uint32_t i = 0; i < physical_device_count; ++i) { | |
VkPhysicalDeviceProperties physical_device_properties = {}; | |
vkGetPhysicalDeviceProperties(physical_devices[i], &physical_device_properties); | |
if(physical_device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { | |
g_vulkanState.selected_physical_device = physical_devices[i]; | |
found_discrete = true; | |
break; | |
} | |
} | |
} | |
} | |
// find the index of the first graphics queue | |
uint32_t queue_family_index = 0; | |
{ | |
const uint8_t MAX_QUEUES = 10; | |
uint32_t num_queue_families = MAX_QUEUES; | |
VkQueueFamilyProperties queue_properties[MAX_QUEUES] = {}; | |
vkGetPhysicalDeviceQueueFamilyProperties(g_vulkanState.selected_physical_device, &num_queue_families, queue_properties); | |
for(uint32_t i = 0; i < num_queue_families; ++i) { | |
if((queue_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == VK_QUEUE_GRAPHICS_BIT) { | |
VkBool32 surface_supported = VK_FALSE; | |
vkGetPhysicalDeviceSurfaceSupportKHR(g_vulkanState.selected_physical_device, i, | |
g_vulkanState.surface, | |
&surface_supported); | |
if(surface_supported) { | |
queue_family_index = i; | |
break; | |
} | |
} | |
} | |
} | |
// create device | |
{ | |
VkDeviceQueueCreateInfo queue_create_info = {}; | |
const float queue_priorities[] = { 1.0 }; | |
queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
queue_create_info.queueFamilyIndex = queue_family_index; | |
queue_create_info.queueCount = 1; | |
queue_create_info.pQueuePriorities = queue_priorities; | |
const char *enabled_device_extension_names[] = { | |
"VK_KHR_swapchain", | |
}; | |
uint32_t enabled_device_extension_count = sizeof(enabled_device_extension_names) / | |
sizeof(enabled_device_extension_names[0]); | |
VkDeviceCreateInfo device_create_info = {}; | |
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | |
device_create_info.queueCreateInfoCount = 1; | |
device_create_info.pQueueCreateInfos = &queue_create_info; | |
device_create_info.enabledExtensionCount = enabled_device_extension_count; | |
device_create_info.ppEnabledExtensionNames = enabled_device_extension_names; | |
vkCreateDevice(g_vulkanState.selected_physical_device, &device_create_info, nullptr, &g_vulkanState.device); | |
} | |
// create command pool | |
VkCommandPool command_pool = {}; | |
{ | |
VkCommandPoolCreateInfo command_pool_create_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO }; | |
command_pool_create_info.queueFamilyIndex = queue_family_index; | |
command_pool_create_info.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | | |
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; | |
vkCreateCommandPool(g_vulkanState.device, &command_pool_create_info, nullptr, &command_pool); | |
} | |
// allocate the setup command buffer | |
{ | |
VkCommandBufferAllocateInfo setup_command_buffer_allocate_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; | |
setup_command_buffer_allocate_info.commandPool = command_pool; | |
setup_command_buffer_allocate_info.commandBufferCount = 1; | |
vkAllocateCommandBuffers(g_vulkanState.device, &setup_command_buffer_allocate_info, &g_vulkanState.setup_command_buffer); | |
} | |
vkGetDeviceQueue(g_vulkanState.device, 0, 0, &g_vulkanState.queue); | |
// create swapchain | |
create_swapchain(); | |
VkFence image_acquired_fence = {}; | |
{ | |
VkFenceCreateInfo fence_create_info = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO }; | |
vkCreateFence(g_vulkanState.device, &fence_create_info, nullptr, &image_acquired_fence); | |
} | |
uint64_t frame_number = 0; | |
bool should_quit = false; | |
while(!should_quit) { | |
printf("%lu\n", frame_number); | |
++frame_number; | |
// process window messages | |
{ | |
#ifdef _WIN32 | |
MSG message = {}; | |
while(PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { | |
switch (message.message) { | |
case (WM_QUIT): { | |
should_quit = true; | |
break; | |
} | |
default: { | |
DispatchMessage(&message); | |
break; | |
} | |
} | |
} | |
#elif __linux__ | |
while(xcb_generic_event_t* event = xcb_poll_for_event(xcb_connection)) { | |
switch (event->response_type & ~0x80) { | |
case XCB_EXPOSE: { | |
xcb_flush(xcb_connection); | |
break; | |
} | |
case XCB_CLIENT_MESSAGE: { | |
if(((xcb_client_message_event_t*)event)->data.data32[0] == wm_delete_window) { | |
should_quit = true; | |
} | |
break; | |
} | |
} | |
free(event); | |
} | |
} | |
#else | |
#endif | |
// acquire the swapchain image | |
uint32_t swapchain_image_index = 0; | |
{ | |
VkResult result = vkAcquireNextImageKHR(g_vulkanState.device, g_vulkanState.swapchain, | |
500000000, nullptr, image_acquired_fence, | |
&swapchain_image_index); | |
if(vkWaitForFences(g_vulkanState.device, 1, | |
&image_acquired_fence, VK_TRUE, 500000000) == VK_TIMEOUT) { | |
printf("Timed out waiting for acquired fence signal\n"); | |
abort(); | |
}; | |
vkResetFences(g_vulkanState.device, 1, &image_acquired_fence); | |
if(result == VK_TIMEOUT) { | |
printf("Timed out waiting for swapchain image\n"); | |
abort(); | |
} | |
if(result == VK_SUBOPTIMAL_KHR) { | |
create_swapchain(); | |
continue; | |
} | |
} | |
// present the swapchain image | |
{ | |
VkPresentInfoKHR present_info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; | |
present_info.pSwapchains = &g_vulkanState.swapchain; | |
present_info.swapchainCount = 1; | |
present_info.pImageIndices = &swapchain_image_index; | |
VkResult result = vkQueuePresentKHR(g_vulkanState.queue, &present_info); | |
if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { | |
create_swapchain(); | |
vkResetFences(g_vulkanState.device, 1, &image_acquired_fence); | |
continue; | |
} | |
} | |
} | |
// destroy all vulkan resources | |
{ | |
vkDestroySwapchainKHR(g_vulkanState.device, g_vulkanState.swapchain, nullptr); | |
vkDestroyFence(g_vulkanState.device, image_acquired_fence, nullptr); | |
vkDestroySurfaceKHR(instance, g_vulkanState.surface, nullptr); | |
vkDestroyCommandPool(g_vulkanState.device, command_pool, nullptr); | |
vkDestroyDevice(g_vulkanState.device, nullptr); | |
vkDestroyInstance(instance, nullptr); | |
#ifdef __linux__ | |
xcb_destroy_window(xcb_connection, xcb_window); | |
xcb_flush(xcb_connection); | |
xcb_disconnect(xcb_connection); | |
#endif | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment