Guest User

Vulkan nVidia Presentation Queue Fail Case

a guest
May 20th, 2016
111
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 17.35 KB | None | 0 0
  1. /*
  2.  *  Minimum test case I could come up with. No return values checked, no error handling,
  3.  *  and assumes the exact capabilities of my setup (detailed below). And yeah, it's still
  4.  *  pretty big. Yay Vulkan verbosity.
  5.  *
  6.  * Problem:
  7.  *  When using more than two different queues (one after another, no multi-threading
  8.  *  needed) for the presenting of swapchain images, things grind to a halt. I first
  9.  *  encountered this using multiple swapchains with multiple surfaces, using one unique
  10.  *  queue per swapchain. But the same trouble occurs when using only a single swapchain.
  11.  *  The first six frames render properly, no matter the combination of swapchain size
  12.  *  and number of queues used.
  13.  *
  14.  *  See the comments for the two defines QUEUECOUNT and SWAPCHAINSIZE, and change their
  15.  *  values to test the breaking cases. Changing presentation modes has had no effect
  16.  *  other than immediate mode finishing the first six frames more quickly than FIFO mode,
  17.  *  before stalling.
  18.  *
  19.  *  Problem found on a GTX 780, with nVidia drivers 364.19 for Linux, Linux kernel 4.5.4,
  20.  *  on X11 with Xfce as display manager. And a 4Ghz Intel i7 CPU, in case it's a timing
  21.  *  issue for the first few presented images. Compiled with GCC 5.3.0.
  22.  *
  23.  *  Build depends on SDL2, X11, and Vulkan.
  24.  *
  25.  * Compiled with:
  26.  *  g++ -std=c++14 -O3 -I/path/to/vulkan/include/ -lSDL2 -lvulkan -o broken main.cpp
  27.  *
  28.  *  Different optimization levels had no effect.
  29.  *
  30.  *  Warning: When left to its own devices with 3 or more queues, this program makes my
  31.  *  machine completely unresponsive. Use something like the Linux 'timeout' tool to send
  32.  *  a SIGKILL after a minute or two, or you're gonna end up power cycling.
  33.  */
  34.  
  35. #include <iostream>
  36.  
  37. #include "SDL2/SDL.h"
  38. #include "SDL2/SDL_syswm.h"
  39. #define VK_USE_PLATFORM_XLIB_KHR
  40. #include "vulkan/vulkan.h"
  41.  
  42. /* Works fine with a queue count of 1 or 2, breaks at 3 or anything higher. */
  43. #define QUEUECOUNT 3
  44. /*
  45.  * Failure behavior seems affected by the number of images in the swapchain.
  46.  * At 2 images, the program hangs while waiting for the command buffer fence.
  47.  * At 3 or more images, the program exits with a device lost error on command submit.
  48.  */
  49. #define SWAPCHAINSIZE 2
  50.  
  51. // Global all the things.
  52. VkDevice renderDevice;
  53. VkSwapchainKHR swapchains[1];
  54. VkSemaphore swapchainSemaphores[1];
  55. VkSemaphore signalSemaphores[1];
  56. VkFence fences[1];
  57. VkCommandBuffer commandBuffer[1];
  58.  
  59. // Prototypes so you don't have to scroll all the way down to main. You're welcome.
  60. bool shouldTerminate();
  61. void init();
  62. SDL_Window* initSDL();
  63. VkInstance initVulkanInstance();
  64. VkSurfaceKHR initVulkanSurface(VkInstance instance, SDL_Window* sdlwindow);
  65. void initVulkanRenderDevice(VkInstance instance);
  66. void initVulkanSwapchain(VkSurfaceKHR surface);
  67. void initVulkanCommandBuffers();
  68. void initVulkanSynchronizationPrimitives();
  69.  
  70. /* Look at me, I'm important! */
  71. int main(int, const char**)
  72. {
  73.     init();
  74.  
  75.     // Bunch of variable filling, blah blah, skip to the loop below this.
  76.     uint32_t imageIndex = 0, roundRobin = 0, frameCount = 0;
  77.     VkClearColorValue clearColor = {{0.6, 0.2, 0.2, 1.0}};
  78.     VkImage imageArray[SWAPCHAINSIZE];
  79.     uint32_t imageCount = SWAPCHAINSIZE;
  80.     vkGetSwapchainImagesKHR(renderDevice, swapchains[0], &imageCount, imageArray);
  81.     VkPipelineStageFlags stages[1] = {VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT};
  82.     VkCommandBufferBeginInfo bufferBeginInfo;
  83.     bufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
  84.     bufferBeginInfo.pNext = NULL;
  85.     bufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
  86.     bufferBeginInfo.pInheritanceInfo = NULL;
  87.     VkImageSubresourceRange subRanges;
  88.     subRanges.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
  89.     subRanges.baseMipLevel = 0;
  90.     subRanges.levelCount = 1;
  91.     subRanges.baseArrayLayer = 0;
  92.     subRanges.layerCount = 1;
  93.     VkImageMemoryBarrier imageBarriers[1];
  94.     imageBarriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
  95.     imageBarriers[0].pNext = NULL;
  96.     imageBarriers[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  97.     imageBarriers[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
  98.     imageBarriers[0].image = imageArray[imageIndex];
  99.     imageBarriers[0].subresourceRange = subRanges;
  100.     VkSubmitInfo submitInfo;
  101.     submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
  102.     submitInfo.pNext = NULL;
  103.     submitInfo.waitSemaphoreCount = 1;
  104.     submitInfo.pWaitSemaphores = swapchainSemaphores;
  105.     submitInfo.pWaitDstStageMask = stages;
  106.     submitInfo.commandBufferCount = 1;
  107.     submitInfo.signalSemaphoreCount = 1;
  108.     submitInfo.pSignalSemaphores = signalSemaphores;
  109.     VkPresentInfoKHR presentInfo;
  110.     presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
  111.     presentInfo.pNext = NULL;
  112.     presentInfo.waitSemaphoreCount = 1;
  113.     presentInfo.pWaitSemaphores = signalSemaphores;
  114.     presentInfo.swapchainCount = 1;
  115.     presentInfo.pSwapchains = swapchains;
  116.     presentInfo.pResults = NULL;
  117.  
  118.     VkQueue queues[16];
  119.  
  120.     // Prefetch handles for all queues.
  121.     for (int i = 0; i < 16; ++i)
  122.         vkGetDeviceQueue(renderDevice, 0, i, &queues[i]);
  123.  
  124.     // Run until escape is pressed, the window is closed, VK_ERROR_DEVICE_LOST is encountered,
  125.     // or, well, forever, if we hang on the fence.
  126.     while (!shouldTerminate())
  127.     {
  128.         // Reset the buffer, fetch the image (which never blocks), and record the simple commands to clear the image.
  129.         vkResetCommandBuffer(commandBuffer[0], 0);
  130.         vkAcquireNextImageKHR(renderDevice, swapchains[0], 0, swapchainSemaphores[0], VK_NULL_HANDLE, &imageIndex);
  131.         vkBeginCommandBuffer(commandBuffer[0], &bufferBeginInfo);
  132.         imageBarriers[0].srcAccessMask = 0;
  133.         imageBarriers[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
  134.         imageBarriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  135.         imageBarriers[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
  136.         vkCmdPipelineBarrier(commandBuffer[0], VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
  137.                              0, 0, NULL, 0, NULL, 1, imageBarriers);
  138.         vkCmdClearColorImage(commandBuffer[0], imageArray[imageIndex], VK_IMAGE_LAYOUT_GENERAL,
  139.                              &clearColor, 1, &subRanges);
  140.         imageBarriers[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
  141.         imageBarriers[0].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
  142.         imageBarriers[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
  143.         imageBarriers[0].newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
  144.         vkCmdPipelineBarrier(commandBuffer[0], VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
  145.                              0, 0, NULL, 0, NULL, 1, imageBarriers);
  146.         vkEndCommandBuffer(commandBuffer[0]);
  147.  
  148.         submitInfo.pCommandBuffers = commandBuffer;
  149.         VkQueue queue = queues[roundRobin];
  150.  
  151.         if (vkQueueSubmit(queue, 1, &submitInfo, fences[0]) == VK_ERROR_DEVICE_LOST)
  152.         {
  153.             std::cout << "Failed.\nvkQueueSubmit() returned VK_ERROR_DEVICE_LOST\n" << std::endl;
  154.             abort();
  155.         }
  156.  
  157.         // When this hangs, it hangs. The GPU seems to lock right up, so be sure to have a watchdog that
  158.         // kills the program after some sane period.
  159.         std::cout << "Waiting for fence... " << std::flush;
  160.         while (vkWaitForFences(renderDevice, 1, fences, VK_TRUE, 100) == VK_TIMEOUT);
  161.         std::cout << "Done.\n";
  162.  
  163.         vkResetFences(renderDevice, 1, fences);
  164.  
  165.         uint32_t imageIndices[1] = {imageIndex};
  166.         presentInfo.pImageIndices = imageIndices;
  167.  
  168.         // This is the most common point of stalling when using more than two queues for presentation.
  169.         std::cout << "Waiting for present... " << std::flush;
  170.         if (vkQueuePresentKHR(queue, &presentInfo) == VK_ERROR_DEVICE_LOST)
  171.         {
  172.             std::cout << "Failed.\nvkQueuePresentKHR() returned VK_ERROR_DEVICE_LOST\n" << std::endl;;
  173.             abort();
  174.         }
  175.         std::cout << "Done.\n";
  176.  
  177.         std::cout << "Waiting for queue idle... " << std::flush;
  178.         vkQueueWaitIdle(queue);
  179.         std::cout << "Done.\n";
  180.  
  181.         ++frameCount;
  182.         std::cout << "Finished frame " << frameCount << " on queue " << roundRobin << " for image with index "
  183.                   << imageIndex << "\n" << std::endl;
  184.  
  185.         ++roundRobin;
  186.         if (roundRobin >= QUEUECOUNT)
  187.             roundRobin = 0;
  188.     }
  189.  
  190.     return 0;
  191. }
  192.  
  193. bool shouldTerminate()
  194. {
  195.     SDL_Event event;
  196.  
  197.     while (SDL_PollEvent(&event))
  198.     {
  199.         switch (event.type)
  200.         {
  201.             case SDL_KEYUP:
  202.                 if (event.key.keysym.sym == SDLK_ESCAPE)
  203.                     return true;
  204.                 break;
  205.             case SDL_WINDOWEVENT:
  206.                 if (event.window.event == SDL_WINDOWEVENT_CLOSE)
  207.                     return true;
  208.                 break;
  209.             default:
  210.                 break;
  211.         }
  212.     }
  213.  
  214.     return false;
  215. }
  216.  
  217. void init()
  218. {
  219.     SDL_Window* sdlwindow = initSDL();
  220.     VkInstance instance = initVulkanInstance();
  221.     VkSurfaceKHR surface = initVulkanSurface(instance, sdlwindow);
  222.     initVulkanRenderDevice(instance);
  223.     initVulkanSwapchain(surface);
  224.     initVulkanCommandBuffers();
  225.     initVulkanSynchronizationPrimitives();
  226. }
  227.  
  228. SDL_Window* initSDL()
  229. {
  230.     SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO);
  231.     SDL_Window* sdlwindow = SDL_CreateWindow("Broken", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, 0);
  232.  
  233.     return sdlwindow;
  234. }
  235.  
  236. VkInstance initVulkanInstance()
  237. {
  238.     VkApplicationInfo appInfo = {};
  239.     appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
  240.     appInfo.pNext = NULL;
  241.     appInfo.pApplicationName = NULL;
  242.     appInfo.applicationVersion = 0;
  243.     appInfo.pEngineName = NULL;
  244.     appInfo.engineVersion = 0;
  245.     appInfo.apiVersion = VK_API_VERSION_1_0;
  246.  
  247.     const char* extensions[2] = {VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME};
  248.  
  249.     VkInstanceCreateInfo instanceCreateInfo = {};
  250.     instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
  251.     instanceCreateInfo.pNext = NULL;
  252.     instanceCreateInfo.flags = 0;
  253.     instanceCreateInfo.pApplicationInfo = &appInfo;
  254.     instanceCreateInfo.enabledLayerCount = 0;
  255.     instanceCreateInfo.ppEnabledLayerNames = NULL;
  256.     instanceCreateInfo.enabledExtensionCount = 2;
  257.     instanceCreateInfo.ppEnabledExtensionNames = extensions;
  258.  
  259.     VkInstance instance;
  260.  
  261.     vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
  262.  
  263.     return instance;
  264. }
  265.  
  266. VkSurfaceKHR initVulkanSurface(VkInstance instance, SDL_Window* sdlwindow)
  267. {
  268.     SDL_SysWMinfo info;
  269.     SDL_GetVersion(&info.version);
  270.     SDL_GetWindowWMInfo(sdlwindow, &info);
  271.     Display* x11display = info.info.x11.display;
  272.     Window x11window = info.info.x11.window;
  273.  
  274.     VkXlibSurfaceCreateInfoKHR surfaceInfo;
  275.     surfaceInfo.sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR;
  276.     surfaceInfo.pNext = NULL;
  277.     surfaceInfo.flags = 0;
  278.     surfaceInfo.dpy = x11display;
  279.     surfaceInfo.window = x11window;
  280.  
  281.     VkSurfaceKHR surface;
  282.  
  283.     vkCreateXlibSurfaceKHR(instance, &surfaceInfo, NULL, &surface);
  284.  
  285.     return surface;
  286. }
  287.  
  288. void initVulkanRenderDevice(VkInstance instance)
  289. {
  290.     uint32_t deviceCount = 0;
  291.     vkEnumeratePhysicalDevices(instance, &deviceCount, NULL);
  292.     VkPhysicalDevice devices[deviceCount];
  293.     vkEnumeratePhysicalDevices(instance, &deviceCount, devices);
  294.  
  295.     float queuePriorities[1] = {1.0};
  296.  
  297.     VkDeviceQueueCreateInfo queueInfos[1];
  298.     queueInfos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
  299.     queueInfos[0].pNext = NULL;
  300.     queueInfos[0].flags = 0;
  301.     queueInfos[0].queueFamilyIndex = 0;
  302.     queueInfos[0].queueCount = 16;
  303.     queueInfos[0].pQueuePriorities = queuePriorities;
  304.  
  305.     VkPhysicalDeviceFeatures features;
  306.     features.robustBufferAccess = VK_FALSE;
  307.     features.fullDrawIndexUint32 = VK_FALSE;
  308.     features.imageCubeArray = VK_FALSE;
  309.     features.independentBlend = VK_FALSE;
  310.     features.geometryShader = VK_TRUE;
  311.     features.tessellationShader = VK_TRUE;
  312.     features.sampleRateShading = VK_FALSE;
  313.     features.dualSrcBlend = VK_FALSE;
  314.     features.logicOp = VK_FALSE;
  315.     features.multiDrawIndirect = VK_FALSE;
  316.     features.drawIndirectFirstInstance = VK_FALSE;
  317.     features.depthClamp = VK_FALSE;
  318.     features.depthBiasClamp = VK_FALSE;
  319.     features.fillModeNonSolid = VK_FALSE;
  320.     features.depthBounds = VK_FALSE;
  321.     features.wideLines = VK_FALSE;
  322.     features.largePoints = VK_FALSE;
  323.     features.alphaToOne = VK_FALSE;
  324.     features.multiViewport = VK_TRUE;
  325.     features.samplerAnisotropy = VK_TRUE;
  326.     features.textureCompressionETC2 = VK_FALSE;
  327.     features.textureCompressionASTC_LDR = VK_FALSE;
  328.     features.textureCompressionBC = VK_FALSE;
  329.     features.occlusionQueryPrecise = VK_FALSE;
  330.     features.pipelineStatisticsQuery = VK_FALSE;
  331.     features.vertexPipelineStoresAndAtomics = VK_FALSE;
  332.     features.fragmentStoresAndAtomics = VK_FALSE;
  333.     features.shaderTessellationAndGeometryPointSize = VK_FALSE;
  334.     features.shaderImageGatherExtended = VK_FALSE;
  335.     features.shaderStorageImageExtendedFormats = VK_FALSE;
  336.     features.shaderStorageImageMultisample = VK_FALSE;
  337.     features.shaderStorageImageReadWithoutFormat = VK_FALSE;
  338.     features.shaderStorageImageWriteWithoutFormat = VK_FALSE;
  339.     features.shaderUniformBufferArrayDynamicIndexing = VK_FALSE;
  340.     features.shaderSampledImageArrayDynamicIndexing = VK_FALSE;
  341.     features.shaderStorageBufferArrayDynamicIndexing = VK_FALSE;
  342.     features.shaderStorageImageArrayDynamicIndexing = VK_FALSE;
  343.     features.shaderClipDistance = VK_FALSE;
  344.     features.shaderCullDistance = VK_FALSE;
  345.     features.shaderFloat64 = VK_FALSE;
  346.     features.shaderInt64 = VK_FALSE;
  347.     features.shaderInt16 = VK_FALSE;
  348.     features.shaderResourceResidency = VK_FALSE;
  349.     features.shaderResourceMinLod = VK_FALSE;
  350.     features.sparseBinding = VK_FALSE;
  351.     features.sparseResidencyBuffer = VK_FALSE;
  352.     features.sparseResidencyImage2D = VK_FALSE;
  353.     features.sparseResidencyImage3D = VK_FALSE;
  354.     features.sparseResidency2Samples = VK_FALSE;
  355.     features.sparseResidency4Samples = VK_FALSE;
  356.     features.sparseResidency8Samples = VK_FALSE;
  357.     features.sparseResidency16Samples = VK_FALSE;
  358.     features.sparseResidencyAliased = VK_FALSE;
  359.     features.variableMultisampleRate = VK_FALSE;
  360.     features.inheritedQueries = VK_FALSE;
  361.  
  362.     const char* extensions[1] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};
  363.  
  364.     VkDeviceCreateInfo deviceInfo;
  365.     deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  366.     deviceInfo.pNext = NULL;
  367.     deviceInfo.flags = 0;
  368.     deviceInfo.queueCreateInfoCount = 1;
  369.     deviceInfo.pQueueCreateInfos = queueInfos;
  370.     deviceInfo.enabledLayerCount = 0;
  371.     deviceInfo.ppEnabledLayerNames = NULL;
  372.     deviceInfo.enabledExtensionCount = 1;
  373.     deviceInfo.ppEnabledExtensionNames = extensions;
  374.     deviceInfo.pEnabledFeatures = &features;
  375.  
  376.     vkCreateDevice(devices[0], &deviceInfo, NULL, &renderDevice);
  377. }
  378.  
  379. void initVulkanSwapchain(VkSurfaceKHR surface)
  380. {
  381.     VkSwapchainCreateInfoKHR chainInfo;
  382.     chainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
  383.     chainInfo.pNext = NULL;
  384.     chainInfo.flags = 0;
  385.     chainInfo.surface = surface;
  386.     chainInfo.minImageCount = SWAPCHAINSIZE;
  387.     chainInfo.imageFormat = VK_FORMAT_B8G8R8A8_UNORM;
  388.     chainInfo.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
  389.     chainInfo.imageExtent = {640, 480};
  390.     chainInfo.imageArrayLayers = 1;
  391.     chainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
  392.     chainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
  393.     chainInfo.queueFamilyIndexCount = 0;
  394.     chainInfo.pQueueFamilyIndices = NULL;
  395.     chainInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
  396.     chainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
  397.     // chainInfo.presentMode = VK_PRESENT_MODE_FIFO_KHR;
  398.     chainInfo.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; // Doesn't influence the results.
  399.     chainInfo.clipped = VK_FALSE;
  400.     chainInfo.oldSwapchain = NULL;
  401.  
  402.     vkCreateSwapchainKHR(renderDevice, &chainInfo, NULL, &swapchains[0]);
  403. }
  404.  
  405. void initVulkanCommandBuffers()
  406. {
  407.     VkCommandPoolCreateInfo poolInfo;
  408.     poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
  409.     poolInfo.pNext = NULL;
  410.     poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
  411.     poolInfo.queueFamilyIndex = 0;
  412.  
  413.     VkCommandPool commandPool;
  414.     vkCreateCommandPool(renderDevice, &poolInfo, NULL, &commandPool);
  415.  
  416.     VkCommandBufferAllocateInfo bufferInfo;
  417.     bufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
  418.     bufferInfo.pNext = NULL;
  419.     bufferInfo.commandPool = commandPool;
  420.     bufferInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
  421.     bufferInfo.commandBufferCount = 1;
  422.  
  423.     vkAllocateCommandBuffers(renderDevice, &bufferInfo, commandBuffer);
  424. }
  425.  
  426. void initVulkanSynchronizationPrimitives()
  427. {
  428.     VkSemaphoreCreateInfo semaphoreInfo;
  429.     semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
  430.     semaphoreInfo.pNext = NULL;
  431.     semaphoreInfo.flags = 0;
  432.  
  433.     vkCreateSemaphore(renderDevice, &semaphoreInfo, NULL, &swapchainSemaphores[0]);
  434.     vkCreateSemaphore(renderDevice, &semaphoreInfo, NULL, &signalSemaphores[0]);
  435.  
  436.     VkFenceCreateInfo fenceInfo[1];
  437.     fenceInfo[0].sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
  438.     fenceInfo[0].pNext = NULL;
  439.     fenceInfo[0].flags = 0;
  440.  
  441.     vkCreateFence(renderDevice, fenceInfo, NULL, &fences[0]);
  442. }
Add Comment
Please, Sign In to add comment