#define GLFW_INCLUDE_VULKAN #include #include #include #include #include #include #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define CLAMP(x, min, max) MAX((min), MIN(x, (max))) #define NELEMS(arr) (sizeof(arr) / sizeof(arr[0])) #define QUEUE_FAMILY_PROP_BUFFER_SIZE 16 #define EXT_PROP_BUFFER_SIZE 256 #define SURFACE_FMT_BUFFER_SIZE 64 #define PRESENT_MODE_BUFFER_SIZE 8 #define SWAPCHAIN_IMG_BUFFER_SIZE 4 #define MAX_SHADER_SIZE (8 * 1024) typedef struct { float position[2], colour[3]; } vertex_t; static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; static const char *extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; static const char vert_shader_code[] = { #embed VERT_SHADER_SPV }; static const char frag_shader_code[] = { #embed FRAG_SHADER_SPV }; static const vertex_t vertices[] = { { { 0.0, -0.5 }, { 1.0, 0.0, 0.0 } }, { { 0.5, 0.5 }, { 0.0, 1.0, 0.0 } }, { { -0.5, 0.5 }, { 0.0, 0.0, 1.0 } }, }; #define NEXTENSIONS NELEMS(extensions) static void key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) { (void)window; (void)scancode; (void)mods; switch (key) { case GLFW_KEY_Q: if (action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); break; } } static VkShaderModule load_shader_module(VkDevice dev, const char *code, size_t code_size) { const VkShaderModuleCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = code_size, .pCode = (uint32_t *)code, }; VkShaderModule mod; const VkResult result = vkCreateShaderModule(dev, &create_info, nullptr, &mod); assert(result == VK_SUCCESS); return mod; } int main() { // Set up window glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); GLFWwindow *window = glfwCreateWindow(800, 600, "Foo", nullptr, nullptr); assert(window); // Register input callbacks glfwSetKeyCallback(window, key_callback); // Initialise Vulkan instance. const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "Foo", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_0, }; uint32_t glfw_ext_count = 0; const char **glfw_ext_names = glfwGetRequiredInstanceExtensions(&glfw_ext_count); const VkInstanceCreateInfo inst_create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .enabledExtensionCount = glfw_ext_count, .ppEnabledExtensionNames = glfw_ext_names, .enabledLayerCount = sizeof(layers) / sizeof(layers[0]), .ppEnabledLayerNames = layers, }; VkInstance inst; VkResult result = vkCreateInstance(&inst_create_info, nullptr, &inst); assert(result == VK_SUCCESS); // Create surface VkSurfaceKHR surface; result = glfwCreateWindowSurface(inst, window, nullptr, &surface); assert(result == VK_SUCCESS); // Select a physical device VkPhysicalDevice physical_dev; uint32_t dev_count = 1; result = vkEnumeratePhysicalDevices(inst, &dev_count, &physical_dev); assert(result == VK_SUCCESS || result == VK_INCOMPLETE); // Select queue family uint32_t queue_family_count = QUEUE_FAMILY_PROP_BUFFER_SIZE; VkQueueFamilyProperties queue_family_props[QUEUE_FAMILY_PROP_BUFFER_SIZE]; vkGetPhysicalDeviceQueueFamilyProperties( physical_dev, &queue_family_count, queue_family_props); uint32_t queue_family; bool found_queue_family = false; for (unsigned i = 0; i < queue_family_count; ++i) { if (queue_family_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { VkBool32 surface_support = false; vkGetPhysicalDeviceSurfaceSupportKHR( physical_dev, i, surface, &surface_support); if (surface_support) { found_queue_family = true; queue_family = i; break; } } } assert(found_queue_family); // Check that the physical device supports all extensions uint32_t ext_count = EXT_PROP_BUFFER_SIZE; VkExtensionProperties ext_props[EXT_PROP_BUFFER_SIZE]; result = vkEnumerateDeviceExtensionProperties( physical_dev, nullptr, &ext_count, ext_props); assert(result == VK_SUCCESS); bool supported[NEXTENSIONS] = {}; for (unsigned i = 0; i < ext_count; ++i) { for (unsigned j = 0; j < NEXTENSIONS; ++j) { if (strcmp(extensions[j], ext_props[i].extensionName) == 0) { supported[j] = true; break; } } } for (unsigned j = 0; j < NEXTENSIONS; ++j) { if (!supported[j]) { fprintf(stderr, "Unsupported: %s\n", extensions[j]); assert(false); } } // Create the logical device and get the queue handle. float queue_priority = 1.0; const VkDeviceQueueCreateInfo queue_create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = queue_family, .queueCount = 1, .pQueuePriorities = &queue_priority, }; const VkDeviceCreateInfo dev_create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = &queue_create_info, .queueCreateInfoCount = 1, .enabledExtensionCount = NEXTENSIONS, .ppEnabledExtensionNames = extensions, }; VkDevice dev; result = vkCreateDevice(physical_dev, &dev_create_info, nullptr, &dev); assert(result == VK_SUCCESS); VkQueue queue; vkGetDeviceQueue(dev, queue_family, 0, &queue); // Select a surface format, preferring R8G8B8A8_SRGB with // SRGB_NONLINEAR colour space. uint32_t surface_fmt_count = SURFACE_FMT_BUFFER_SIZE; VkSurfaceFormatKHR surface_fmts[SURFACE_FMT_BUFFER_SIZE]; result = vkGetPhysicalDeviceSurfaceFormatsKHR( physical_dev, surface, &surface_fmt_count, surface_fmts); assert(result == VK_SUCCESS); assert(surface_fmt_count > 0); VkSurfaceFormatKHR surface_fmt = surface_fmts[0]; for (unsigned i = 0; i < surface_fmt_count; ++i) { if (surface_fmts[i].format == VK_FORMAT_R8G8B8A8_SRGB && surface_fmts[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { surface_fmt = surface_fmts[i]; } } // Query surface capabilities VkSurfaceCapabilitiesKHR surface_caps; result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR( physical_dev, surface, &surface_caps); assert(result == VK_SUCCESS); // Select swap chain extent VkExtent2D swapchain_extent; if (surface_caps.currentExtent.width == UINT32_MAX) { int width, height; glfwGetFramebufferSize(window, &width, &height); swapchain_extent.width = CLAMP( (uint32_t)width, surface_caps.minImageExtent.width, surface_caps.maxImageExtent.width); swapchain_extent.height = CLAMP( (uint32_t)height, surface_caps.minImageExtent.height, surface_caps.maxImageExtent.height); } else { swapchain_extent = surface_caps.currentExtent; } // Select image count uint32_t swapchain_img_count = surface_caps.minImageCount + 1; assert( surface_caps.maxImageCount == 0 || swapchain_img_count <= surface_caps.maxImageCount); // Create the swap chain const VkSwapchainCreateInfoKHR swapchain_create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface, .minImageCount = swapchain_img_count, .imageFormat = surface_fmt.format, .imageColorSpace = surface_fmt.colorSpace, .imageExtent = swapchain_extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .preTransform = surface_caps.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = VK_PRESENT_MODE_FIFO_KHR, .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE, }; VkSwapchainKHR swapchain; result = vkCreateSwapchainKHR( dev, &swapchain_create_info, nullptr, &swapchain); assert(result == VK_SUCCESS); // Retreive swapchain images VkImage swapchain_imgs[SWAPCHAIN_IMG_BUFFER_SIZE]; swapchain_img_count = SWAPCHAIN_IMG_BUFFER_SIZE; result = vkGetSwapchainImagesKHR( dev, swapchain, &swapchain_img_count, swapchain_imgs); assert(result == VK_SUCCESS); // Create swapchain image views VkImageView swapchain_img_views[SWAPCHAIN_IMG_BUFFER_SIZE]; VkImageViewCreateInfo img_view_create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = surface_fmt.format, .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY, }, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; for (unsigned i = 0; i < swapchain_img_count; ++i) { img_view_create_info.image = swapchain_imgs[i]; result = vkCreateImageView( dev, &img_view_create_info, nullptr, swapchain_img_views + i); } // Load vertex and fragment shader modules VkShaderModule vert_shader = load_shader_module( dev, vert_shader_code, sizeof(vert_shader_code)); VkShaderModule frag_shader = load_shader_module( dev, frag_shader_code, sizeof(frag_shader_code)); const VkPipelineShaderStageCreateInfo shader_stages[] = { [0] = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vert_shader, .pName = "main", }, [1] = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = frag_shader, .pName = "main", }, }; // Set up input configuration const VkVertexInputBindingDescription input_binding_descs[] = { { .binding = 0, .stride = sizeof(vertex_t), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }, }; const VkVertexInputAttributeDescription input_attribute_descs[] = { { .binding = 0, .location = 0, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(vertex_t, position), }, { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(vertex_t, colour), }, }; const VkPipelineVertexInputStateCreateInfo vertex_input_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = NELEMS(input_binding_descs), .pVertexBindingDescriptions = input_binding_descs, .vertexAttributeDescriptionCount = NELEMS(input_attribute_descs), .pVertexAttributeDescriptions = input_attribute_descs, }; const VkPipelineInputAssemblyStateCreateInfo input_assembly_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, }; // Set up viewport configuration const VkViewport viewport = { .width = (float)swapchain_extent.width, .height = (float)swapchain_extent.height, }; const VkRect2D scissor = { .extent = swapchain_extent }; VkPipelineViewportStateCreateInfo viewport_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor, }; // Populate rasterizer configuration const VkPipelineRasterizationStateCreateInfo rasterizer_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, .lineWidth = 1.0f, .cullMode = VK_CULL_MODE_NONE, }; // Populate multisampling configuration const VkPipelineMultisampleStateCreateInfo multisampling_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, }; // Set up colour blending configuration const VkPipelineColorBlendAttachmentState colour_blend_attatchment = { .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, .blendEnable = VK_FALSE, }; const VkPipelineColorBlendStateCreateInfo colour_blend_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .attachmentCount = 1, .pAttachments = &colour_blend_attatchment, }; // Create pipeline layout const VkPipelineLayoutCreateInfo pipeline_layout_config = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pushConstantRangeCount = 0, }; VkPipelineLayout pipeline_layout; result = vkCreatePipelineLayout( dev, &pipeline_layout_config, nullptr, &pipeline_layout); assert(result == VK_SUCCESS); // Set up colour attachment const VkAttachmentDescription colour_attachment = { .format = surface_fmt.format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, }; const VkAttachmentReference colour_attachment_ref = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; const VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colour_attachment_ref, }; // Create render pass const VkSubpassDependency dependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, }; const VkRenderPassCreateInfo render_pass_config = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &colour_attachment, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency, }; VkRenderPass render_pass; result = vkCreateRenderPass( dev, &render_pass_config, nullptr, &render_pass); assert(result == VK_SUCCESS); // Create pipeline const VkGraphicsPipelineCreateInfo pipeline_config = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = shader_stages, .pVertexInputState = &vertex_input_config, .pInputAssemblyState = &input_assembly_config, .pViewportState = &viewport_config, .pRasterizationState = &rasterizer_config, .pMultisampleState = &multisampling_config, .pColorBlendState = &colour_blend_config, .layout = pipeline_layout, .renderPass = render_pass, .subpass = 0, }; VkPipeline pipeline; result = vkCreateGraphicsPipelines( dev, VK_NULL_HANDLE, 1, &pipeline_config, nullptr, &pipeline); assert(result == VK_SUCCESS); // Create framebuffers VkFramebuffer framebuffers[SWAPCHAIN_IMG_BUFFER_SIZE]; for (unsigned i = 0; i < swapchain_img_count; ++i) { const VkFramebufferCreateInfo framebuffer_config = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = render_pass, .attachmentCount = 1, .pAttachments = swapchain_img_views + i, .width = swapchain_extent.width, .height = swapchain_extent.height, .layers = 1, }; result = vkCreateFramebuffer( dev, &framebuffer_config, nullptr, framebuffers + i); assert(result == VK_SUCCESS); } // Create vertex buffer const VkBufferCreateInfo vertex_buffer_config = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = sizeof(vertices), .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; VkBuffer vertex_buffer; result = vkCreateBuffer( dev, &vertex_buffer_config, nullptr, &vertex_buffer); assert(result == VK_SUCCESS); // Allocate memory to vertex buffer const VkMemoryPropertyFlags mem_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; VkMemoryRequirements mem_requirements; vkGetBufferMemoryRequirements(dev, vertex_buffer, &mem_requirements); VkPhysicalDeviceMemoryProperties mem_props; vkGetPhysicalDeviceMemoryProperties(physical_dev, &mem_props); uint32_t mem_type = UINT32_MAX; for (unsigned i = 0; i < mem_props.memoryTypeCount; ++i) { const bool valid_type = (mem_requirements.memoryTypeBits & (1 << i)) != 0; const bool has_flags = (mem_props.memoryTypes[i].propertyFlags & mem_flags) == mem_flags; if (valid_type && has_flags) { mem_type = i; break; } } assert(mem_type != UINT32_MAX); VkMemoryAllocateInfo allocate_config = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = mem_requirements.size, .memoryTypeIndex = mem_type, }; VkDeviceMemory vertex_buffer_mem; result = vkAllocateMemory( dev, &allocate_config, nullptr, &vertex_buffer_mem); assert(result == VK_SUCCESS); vkBindBufferMemory(dev, vertex_buffer, vertex_buffer_mem, 0); // Copy vertex data into vertex buffer void *vertex_data; vkMapMemory( dev, vertex_buffer_mem, 0, sizeof(vertices), 0, &vertex_data); memcpy(vertex_data, vertices, sizeof(vertices)); vkUnmapMemory(dev, vertex_buffer_mem); // Create command pool const VkCommandPoolCreateInfo cmd_pool_config = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = queue_family, }; VkCommandPool cmd_pool; result = vkCreateCommandPool(dev, &cmd_pool_config, nullptr, &cmd_pool); assert(result == VK_SUCCESS); // Allocate command buffer const VkCommandBufferAllocateInfo cmd_buf_config = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = cmd_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; VkCommandBuffer cmd_buf; result = vkAllocateCommandBuffers(dev, &cmd_buf_config, &cmd_buf); assert(result == VK_SUCCESS); // Create syncronisation primitives VkSemaphore img_avail, render_fin; VkSemaphoreCreateInfo semaphore_config = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; result = vkCreateSemaphore(dev, &semaphore_config, nullptr, &img_avail); assert(result == VK_SUCCESS); result = vkCreateSemaphore(dev, &semaphore_config, nullptr, &render_fin); assert(result == VK_SUCCESS); VkFence in_flight; VkFenceCreateInfo fence_config = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; result = vkCreateFence(dev, &fence_config, nullptr, &in_flight); assert(result == VK_SUCCESS); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); // Wait for previous frame to finish vkWaitForFences(dev, 1, &in_flight, VK_TRUE, UINT64_MAX); vkResetFences(dev, 1, &in_flight); // Aquire image from swapchain uint32_t img_index; result = vkAcquireNextImageKHR( dev, swapchain, UINT64_MAX, img_avail, VK_NULL_HANDLE, &img_index); assert(result == VK_SUCCESS); // Reset command buffer, then begin writing vkResetCommandBuffer(cmd_buf, 0); const VkCommandBufferBeginInfo cmd_begin = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; result = vkBeginCommandBuffer(cmd_buf, &cmd_begin); assert(result == VK_SUCCESS); // Start render pass VkClearValue clear_value = { { { 0.0, 0.0, 0.0, 1.0 } } }; VkRenderPassBeginInfo render_pass_begin = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = render_pass, .framebuffer = framebuffers[img_index], .renderArea.extent = swapchain_extent, .clearValueCount = 1, .pClearValues = &clear_value, }; vkCmdBeginRenderPass( cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); // Bind graphics pipeline and vertex buffer, then draw vkCmdBindPipeline( cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); vkCmdBindVertexBuffers( cmd_buf, 0, 1, &vertex_buffer, &(VkDeviceSize) { 0 }); vkCmdDraw(cmd_buf, NELEMS(vertices), 1, 0, 0); // Finish render pass and end writing to command buffer vkCmdEndRenderPass(cmd_buf); result = vkEndCommandBuffer(cmd_buf); assert(result == VK_SUCCESS); // Submit command buffer VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; const VkSubmitInfo submit = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = &img_avail, .pWaitDstStageMask = &wait_stage, .commandBufferCount = 1, .pCommandBuffers = &cmd_buf, .signalSemaphoreCount = 1, .pSignalSemaphores = &render_fin, }; result = vkQueueSubmit(queue, 1, &submit, in_flight); assert(result == VK_SUCCESS); // Present frame const VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = &render_fin, .swapchainCount = 1, .pSwapchains = &swapchain, .pImageIndices = &img_index, }; result = vkQueuePresentKHR(queue, &present_info); assert(result == VK_SUCCESS); } vkDeviceWaitIdle(dev); // Clean up vkDestroySemaphore(dev, img_avail, nullptr); vkDestroySemaphore(dev, render_fin, nullptr); vkDestroyFence(dev, in_flight, nullptr); vkDestroyCommandPool(dev, cmd_pool, nullptr); vkDestroyPipeline(dev, pipeline, nullptr); vkDestroyRenderPass(dev, render_pass, nullptr); vkDestroyPipelineLayout(dev, pipeline_layout, nullptr); vkDestroyShaderModule(dev, vert_shader, nullptr); vkDestroyShaderModule(dev, frag_shader, nullptr); for (unsigned i = 0; i < swapchain_img_count; ++i) { vkDestroyFramebuffer(dev, framebuffers[i], nullptr); vkDestroyImageView(dev, swapchain_img_views[i], nullptr); } vkDestroySwapchainKHR(dev, swapchain, nullptr); vkDestroyBuffer(dev, vertex_buffer, nullptr); vkFreeMemory(dev, vertex_buffer_mem, nullptr); vkDestroyDevice(dev, nullptr); vkDestroySurfaceKHR(inst, surface, nullptr); vkDestroyInstance(inst, nullptr); glfwDestroyWindow(window); glfwTerminate(); }