Skip to content

Instantly share code, notes, and snippets.

@RoryO
Created March 12, 2021 01:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RoryO/1dec504cab731a9eef5f4f9899c2644d to your computer and use it in GitHub Desktop.
Save RoryO/1dec504cab731a9eef5f4f9899c2644d to your computer and use it in GitHub Desktop.
// 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