#include "gpu.h" #include #include #include #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 NELEMS(arr) (sizeof(arr) / sizeof(arr[0])) #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))) static const char *layers[] = { "VK_LAYER_KHRONOS_validation" }; static const char *extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; static constexpr size_t extension_count = NELEMS(extensions); static const char vert_shader[] = { #embed VERT_SHADER_SPV }; static const char frag_shader[] = { #embed FRAG_SHADER_SPV }; 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; } void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config) { // Initialise Vulkan instance. const VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = config->application_name, .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, }; VkResult result = vkCreateInstance(&inst_create_info, nullptr, &ctx->inst); assert(result == VK_SUCCESS); // Create surface result = glfwCreateWindowSurface( ctx->inst, config->window, nullptr, &ctx->surface); assert(result == VK_SUCCESS); // Select a physical device uint32_t dev_count = 1; result = vkEnumeratePhysicalDevices( ctx->inst, &dev_count, &ctx->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( ctx->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( ctx->physical_dev, i, ctx->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( ctx->physical_dev, nullptr, &ext_count, ext_props); assert(result == VK_SUCCESS); bool supported[extension_count] = {}; for (unsigned i = 0; i < ext_count; ++i) { for (unsigned j = 0; j < extension_count; ++j) { if (strcmp(extensions[j], ext_props[i].extensionName) == 0) { supported[j] = true; break; } } } for (unsigned j = 0; j < extension_count; ++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 = extension_count, .ppEnabledExtensionNames = extensions, }; result = vkCreateDevice( ctx->physical_dev, &dev_create_info, nullptr, &ctx->dev); assert(result == VK_SUCCESS); vkGetDeviceQueue(ctx->dev, queue_family, 0, &ctx->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( ctx->physical_dev, ctx->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( ctx->physical_dev, ctx->surface, &surface_caps); assert(result == VK_SUCCESS); // Select swap chain extent if (surface_caps.currentExtent.width == UINT32_MAX) { int width, height; glfwGetFramebufferSize(config->window, &width, &height); ctx->swapchain_extent.width = CLAMP( (uint32_t)width, surface_caps.minImageExtent.width, surface_caps.maxImageExtent.width); ctx->swapchain_extent.height = CLAMP( (uint32_t)height, surface_caps.minImageExtent.height, surface_caps.maxImageExtent.height); } else { ctx->swapchain_extent = surface_caps.currentExtent; } // Select image count uint32_t swapchain_img_count = surface_caps.minImageCount + 1; assert(swapchain_img_count <= SWAPCHAIN_IMG_BUFFER_SIZE); 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 = ctx->surface, .minImageCount = swapchain_img_count, .imageFormat = surface_fmt.format, .imageColorSpace = surface_fmt.colorSpace, .imageExtent = ctx->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, }; result = vkCreateSwapchainKHR( ctx->dev, &swapchain_create_info, nullptr, &ctx->swapchain); assert(result == VK_SUCCESS); // Retreive swapchain images VkImage swapchain_imgs[SWAPCHAIN_IMG_BUFFER_SIZE]; ctx->swapchain_img_count = SWAPCHAIN_IMG_BUFFER_SIZE; result = vkGetSwapchainImagesKHR( ctx->dev, ctx->swapchain, &swapchain_img_count, swapchain_imgs); assert(result == VK_SUCCESS); // Create swapchain image views 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( ctx->dev, &img_view_create_info, nullptr, ctx->swapchain_img_views + i); assert(result == VK_SUCCESS); } // Load vertex and fragment shader modules ctx->vert_shader = load_shader_module(ctx->dev, vert_shader, sizeof(vert_shader)); ctx->frag_shader = load_shader_module(ctx->dev, frag_shader, sizeof(frag_shader)); const VkPipelineShaderStageCreateInfo shader_stages[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = ctx->vert_shader, .pName = "main", }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = ctx->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)ctx->swapchain_extent.width, .height = (float)ctx->swapchain_extent.height, }; const VkRect2D scissor = { .extent = ctx->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, }; result = vkCreatePipelineLayout( ctx->dev, &pipeline_layout_config, nullptr, &ctx->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, }; result = vkCreateRenderPass( ctx->dev, &render_pass_config, nullptr, &ctx->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 = ctx->pipeline_layout, .renderPass = ctx->render_pass, .subpass = 0, }; result = vkCreateGraphicsPipelines( ctx->dev, VK_NULL_HANDLE, 1, &pipeline_config, nullptr, &ctx->pipeline); assert(result == VK_SUCCESS); // Create framebuffers for (unsigned i = 0; i < swapchain_img_count; ++i) { const VkFramebufferCreateInfo framebuffer_config = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = ctx->render_pass, .attachmentCount = 1, .pAttachments = ctx->swapchain_img_views + i, .width = ctx->swapchain_extent.width, .height = ctx->swapchain_extent.height, .layers = 1, }; result = vkCreateFramebuffer( ctx->dev, &framebuffer_config, nullptr, ctx->framebuffers + i); assert(result == VK_SUCCESS); } // Create vertex buffer ctx->vertex_data_size = config->vertex_data_size; const VkBufferCreateInfo vertex_buf_config = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = ctx->vertex_data_size, .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; result = vkCreateBuffer( ctx->dev, &vertex_buf_config, nullptr, &ctx->vertex_buf); 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( ctx->dev, ctx->vertex_buf, &mem_requirements); VkPhysicalDeviceMemoryProperties mem_props; vkGetPhysicalDeviceMemoryProperties(ctx->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, }; result = vkAllocateMemory( ctx->dev, &allocate_config, nullptr, &ctx->vertex_buf_mem); assert(result == VK_SUCCESS); vkBindBufferMemory(ctx->dev, ctx->vertex_buf, ctx->vertex_buf_mem, 0); // Copy vertex data into vertex buffer void *vertex_buf_ptr; vkMapMemory( ctx->dev, ctx->vertex_buf_mem, 0, ctx->vertex_data_size, 0, &vertex_buf_ptr); memcpy(vertex_buf_ptr, config->vertex_data, ctx->vertex_data_size); vkUnmapMemory(ctx->dev, ctx->vertex_buf_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, }; result = vkCreateCommandPool( ctx->dev, &cmd_pool_config, nullptr, &ctx->cmd_pool); assert(result == VK_SUCCESS); // Allocate command buffer const VkCommandBufferAllocateInfo cmd_buf_config = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = ctx->cmd_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; result = vkAllocateCommandBuffers(ctx->dev, &cmd_buf_config, &ctx->cmd_buf); assert(result == VK_SUCCESS); // Create syncronisation primitives VkSemaphoreCreateInfo semaphore_config = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; result = vkCreateSemaphore( ctx->dev, &semaphore_config, nullptr, &ctx->img_avail); assert(result == VK_SUCCESS); result = vkCreateSemaphore( ctx->dev, &semaphore_config, nullptr, &ctx->render_fin); assert(result == VK_SUCCESS); VkFenceCreateInfo fence_config = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; result = vkCreateFence(ctx->dev, &fence_config, nullptr, &ctx->in_flight); assert(result == VK_SUCCESS); } void gpu_cleanup(const gpu_ctx_t *ctx) { vkDeviceWaitIdle(ctx->dev); vkDestroySemaphore(ctx->dev, ctx->img_avail, nullptr); vkDestroySemaphore(ctx->dev, ctx->render_fin, nullptr); vkDestroyFence(ctx->dev, ctx->in_flight, nullptr); vkDestroyCommandPool(ctx->dev, ctx->cmd_pool, nullptr); vkDestroyPipeline(ctx->dev, ctx->pipeline, nullptr); vkDestroyRenderPass(ctx->dev, ctx->render_pass, nullptr); vkDestroyPipelineLayout(ctx->dev, ctx->pipeline_layout, nullptr); vkDestroyShaderModule(ctx->dev, ctx->vert_shader, nullptr); vkDestroyShaderModule(ctx->dev, ctx->frag_shader, nullptr); for (unsigned i = 0; i < ctx->swapchain_img_count; ++i) { vkDestroyFramebuffer(ctx->dev, ctx->framebuffers[i], nullptr); vkDestroyImageView(ctx->dev, ctx->swapchain_img_views[i], nullptr); } vkDestroySwapchainKHR(ctx->dev, ctx->swapchain, nullptr); vkDestroyBuffer(ctx->dev, ctx->vertex_buf, nullptr); vkFreeMemory(ctx->dev, ctx->vertex_buf_mem, nullptr); vkDestroyDevice(ctx->dev, nullptr); vkDestroySurfaceKHR(ctx->inst, ctx->surface, nullptr); vkDestroyInstance(ctx->inst, nullptr); } void gpu_render(const gpu_ctx_t *ctx) { VkResult result; // Wait for previous frame to finish vkWaitForFences(ctx->dev, 1, &ctx->in_flight, VK_TRUE, UINT64_MAX); vkResetFences(ctx->dev, 1, &ctx->in_flight); // Aquire image from swapchain uint32_t img_index; result = vkAcquireNextImageKHR( ctx->dev, ctx->swapchain, UINT64_MAX, ctx->img_avail, VK_NULL_HANDLE, &img_index); assert(result == VK_SUCCESS); // Reset command buffer, then begin writing vkResetCommandBuffer(ctx->cmd_buf, 0); const VkCommandBufferBeginInfo cmd_begin = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; result = vkBeginCommandBuffer(ctx->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 = ctx->render_pass, .framebuffer = ctx->framebuffers[img_index], .renderArea.extent = ctx->swapchain_extent, .clearValueCount = 1, .pClearValues = &clear_value, }; vkCmdBeginRenderPass( ctx->cmd_buf, &render_pass_begin, VK_SUBPASS_CONTENTS_INLINE); // Bind graphics pipeline and vertex buffer, then draw vkCmdBindPipeline( ctx->cmd_buf, VK_PIPELINE_BIND_POINT_GRAPHICS, ctx->pipeline); vkCmdBindVertexBuffers( ctx->cmd_buf, 0, 1, &ctx->vertex_buf, &(VkDeviceSize) { 0 }); vkCmdDraw( ctx->cmd_buf, ctx->vertex_data_size / sizeof(vertex_t), 1, 0, 0); // Finish render pass and end writing to command buffer vkCmdEndRenderPass(ctx->cmd_buf); result = vkEndCommandBuffer(ctx->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 = &ctx->img_avail, .pWaitDstStageMask = &wait_stage, .commandBufferCount = 1, .pCommandBuffers = &ctx->cmd_buf, .signalSemaphoreCount = 1, .pSignalSemaphores = &ctx->render_fin, }; result = vkQueueSubmit(ctx->queue, 1, &submit, ctx->in_flight); assert(result == VK_SUCCESS); // Present frame const VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = &ctx->render_fin, .swapchainCount = 1, .pSwapchains = &ctx->swapchain, .pImageIndices = &img_index, }; result = vkQueuePresentKHR(ctx->queue, &present_info); assert(result == VK_SUCCESS); }