#include #include #include #include #include #include #include #include #include #include #include VkVertexInputBindingDescription Vertex::binding_description() { return { .binding = 0, .stride = sizeof(Vertex), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }; } std::array Vertex::attributes_description() { return { VkVertexInputAttributeDescription { .location = 0, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, position), }, { .location = 1, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, color), }, }; } const std::vector vertices = { {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}}, {{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}}, {{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f, 1.0f}}, {{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f, 1.0f}}, {{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f, 1.0f}}, {{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f, 0.0f}}, {{0.5f, 0.5f, 0.5f}, {0.2f, 0.2f, 0.2f}}, }; const std::vector indices = { 0, 1, 2, 2, 1, 3, 1, 5, 3, 3, 5, 7, 5, 4, 7, 7, 4, 6, 4, 0, 6, 6, 0, 2, 0, 4, 1, 1, 4, 5, 2, 3, 6, 6, 3, 7, }; struct Uniforms { alignas(16) Eigen::Matrix4f scene_from_model; alignas(16) Eigen::Matrix4f projection_from_scene; }; VulkanTutorial::VulkanTutorial() { m_swapchain.register_creation_callback( std::bind(&VulkanTutorial::create_swapchain_objects, this, std::placeholders::_1)); m_swapchain.register_destruction_callback( std::bind(&VulkanTutorial::destroy_swapchain_objects, this)); } VulkanTutorial::~VulkanTutorial() { shutdown(); } void VulkanTutorial::initialize(SDL_Window* window) { auto const context_settings = Vulkan::ContextSettings() #if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG) .with_debug(true) #endif .with_queue(GRAPHIC_QUEUE, VK_QUEUE_GRAPHICS_BIT) .with_window(window); m_context.initialize(context_settings); create_command_pool(); create_descriptor_set_layout(); create_vertex_buffer(); create_index_buffer(); auto const swapchain_settings = Vulkan::SwapchainSettings(&m_context) .with_queue(m_context.queue_family(GRAPHIC_QUEUE)); m_swapchain.initialize(swapchain_settings); } void VulkanTutorial::shutdown() { if(!m_context.instance()) return; vkDeviceWaitIdle(m_context.device()); destroy_swapchain_objects(); m_context.free_memory(m_vertex_buffer_memory); m_context.destroy_command_pool(m_command_pool); m_context.free_memory(m_index_buffer_memory); m_context.destroy_buffer(m_index_buffer); m_context.free_memory(m_vertex_buffer_memory); m_context.destroy_buffer(m_vertex_buffer); m_context.destroy_descriptor_set_layout(m_descriptor_set_layout); for(VkSemaphore semaphore: m_render_done) m_context.destroy_semaphore(semaphore); m_render_done.clear(); m_swapchain.shutdown(); m_context.shutdown(); } void VulkanTutorial::draw_frame() { m_swapchain.begin_frame(); auto const image_index = m_swapchain.current_image_index(); const auto now = Clock::now(); if(m_last_frame_time.time_since_epoch() != Duration(0)) { m_time += now - m_last_frame_time; } m_last_frame_time = now; const float alpha = SecondsD(m_time).count() * (2.0 * M_PI) / 3.0; const float dist = 2.0f; const Eigen::Matrix4f view = look_at_matrix( // Eigen::Vector3f(0.0f, 0.0f, -dist), Eigen::Vector3f(0.0f, -dist, -dist), // dist * Eigen::Vector3f(std::cos(alpha), std::sin(alpha), -1.0), Eigen::Vector3f::Zero(), -Eigen::Vector3f::UnitY() ); const float fov = M_PI / 3.0f; const float near = 0.1f; const float far = 10.0f; const float hx = near * std::tan(fov / 2.0f); const float hy = hx * m_swapchain.extent().height / m_swapchain.extent().width; const Eigen::Matrix4f proj = projection_matrix( -hx, hx, -hy, hy, near, far ); using Transform = Eigen::Transform; Transform model = Transform::Identity(); model.rotate(Eigen::AngleAxisf( alpha, Eigen::Vector3f::UnitY() )); const Uniforms uniforms = { .scene_from_model = model.matrix(), .projection_from_scene = proj * view, }; void* uniform_buffer; vkMapMemory( m_context.device(), m_uniform_buffer_memory, m_uniform_buffer_offsets[image_index], sizeof(Uniforms), 0, &uniform_buffer ); std::memcpy(uniform_buffer, &uniforms, sizeof(Uniforms)); vkUnmapMemory(m_context.device(), m_uniform_buffer_memory); VkSemaphore wait_semaphores[] = { m_swapchain.ready_to_render(), }; VkPipelineStageFlags stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, }; VkSubmitInfo submit_info { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = wait_semaphores, .pWaitDstStageMask = stages, .commandBufferCount = 1, .pCommandBuffers = &m_command_buffers[image_index], .signalSemaphoreCount = 1, .pSignalSemaphores = &m_render_done[image_index], }; if(vkQueueSubmit( m_context.queue(GRAPHIC_QUEUE), 1, &submit_info, m_swapchain.render_done() )) throw std::runtime_error("failed to submit draw command buffer"); m_swapchain.swap_buffers({1, &m_render_done[image_index]}); } void VulkanTutorial::invalidate_swapchain() { m_swapchain.invalidate(); } void VulkanTutorial::create_swapchain_objects(uint32_t image_count) { create_render_pass(); create_framebuffers(); create_uniform_buffer(); create_descriptor_pool(); create_descriptor_sets(); create_graphic_pipeline(); create_command_buffers(); m_render_done.resize(m_swapchain.image_count()); VkSemaphoreCreateInfo semaphore_info { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; for(size_t index = 0; index < m_render_done.size(); index += 1) { if(vkCreateSemaphore( m_context.device(), &semaphore_info, nullptr, &m_render_done[index] ) != VK_SUCCESS) throw std::runtime_error("failed to create semaphore"); } } void VulkanTutorial::destroy_swapchain_objects() { if(m_command_pool == VK_NULL_HANDLE) return; vkFreeCommandBuffers( m_context.device(), m_command_pool, uint32_t(m_command_buffers.size()), m_command_buffers.data() ); m_command_buffers.clear(); for(VkBuffer buffer: m_uniform_buffers) m_context.destroy_buffer(buffer); m_uniform_buffers.clear(); m_context.free_memory(m_uniform_buffer_memory); m_context.destroy_descriptor_pool(m_descriptor_pool); for(VkFramebuffer framebuffer: m_framebuffers) m_context.destroy_framebuffer(framebuffer); m_framebuffers.clear(); m_context.destroy_pipeline(m_pipeline); m_context.destroy_pipeline_layout(m_pipeline_layout); m_context.destroy_render_pass(m_render_pass); } void VulkanTutorial::create_render_pass() { VkAttachmentDescription color_attachment { .format = m_context.surface_format().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, }; VkAttachmentReference color_attachment_ref = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkSubpassDescription subpass { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &color_attachment_ref, }; VkSubpassDependency subpass_dep = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, }; VkRenderPassCreateInfo render_pass_info { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &color_attachment, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &subpass_dep, }; if(vkCreateRenderPass( m_context.device(), &render_pass_info, nullptr, &m_render_pass ) != VK_SUCCESS) throw std::runtime_error("failed to create render pass"); } void VulkanTutorial::create_descriptor_set_layout() { VkDescriptorSetLayoutBinding binding { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, }; VkDescriptorSetLayoutCreateInfo layout_info { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 1, .pBindings = &binding, }; if(vkCreateDescriptorSetLayout( m_context.device(), &layout_info, nullptr, &m_descriptor_set_layout ) != VK_SUCCESS) throw std::runtime_error("failed to create descriptor set layout"); } void VulkanTutorial::create_graphic_pipeline() { auto const vertex_shader_module = m_context.create_shader_module_from_file("shaders/shader.vert.spv"); auto const vertex_shader_guard = make_guard([&]{ vkDestroyShaderModule(m_context.device(), vertex_shader_module, nullptr); }); auto const fragment_shader_module = m_context.create_shader_module_from_file("shaders/shader.frag.spv"); auto const fragment_shader_guard = make_guard([&]{ vkDestroyShaderModule(m_context.device(), fragment_shader_module, nullptr); }); VkPipelineShaderStageCreateInfo shader_stage_infos[] = { { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vertex_shader_module, .pName = "main", }, { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = fragment_shader_module, .pName = "main", }, }; const VkVertexInputBindingDescription vertex_bindings[] = { Vertex::binding_description(), }; const auto vertex_attributes = Vertex::attributes_description(); VkPipelineVertexInputStateCreateInfo vertex_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = vertex_bindings, .vertexAttributeDescriptionCount = uint32_t(vertex_attributes.size()), .pVertexAttributeDescriptions = vertex_attributes.data(), }; VkPipelineInputAssemblyStateCreateInfo assembly_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = VK_FALSE, }; VkViewport viewport { .x = 0.0f, .y = 0.0f, .width = float(m_swapchain.extent().width), .height = float(m_swapchain.extent().height), .minDepth = 0.0f, .maxDepth = 1.0f, }; VkRect2D scissor = { .offset = { 0, 0 }, .extent = m_swapchain.extent(), }; VkPipelineViewportStateCreateInfo viewport_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor, }; VkPipelineRasterizationStateCreateInfo rasterization_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, .depthBiasClamp = 0.0f, .depthBiasSlopeFactor = 0.0f, .lineWidth = 1.0f, }; VkPipelineMultisampleStateCreateInfo multisample_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, .sampleShadingEnable = VK_FALSE, .minSampleShading = 1.0f, .pSampleMask = nullptr, .alphaToCoverageEnable = VK_FALSE, .alphaToOneEnable = VK_FALSE, }; VkPipelineColorBlendAttachmentState color_blend_attachment { .blendEnable = VK_FALSE, .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, .colorBlendOp = VK_BLEND_OP_ADD, .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, .alphaBlendOp = VK_BLEND_OP_ADD, .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, }; VkPipelineColorBlendStateCreateInfo color_blend_info { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .logicOp = VK_LOGIC_OP_COPY, .attachmentCount = 1, .pAttachments = &color_blend_attachment, .blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f }, }; VkPipelineLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &m_descriptor_set_layout, .pushConstantRangeCount = 0, .pPushConstantRanges = nullptr, }; if(vkCreatePipelineLayout( m_context.device(), &layout_info, nullptr, &m_pipeline_layout ) != VK_SUCCESS) throw std::runtime_error("failed to create pipeline layout"); VkGraphicsPipelineCreateInfo pipeline_info { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = shader_stage_infos, .pVertexInputState = &vertex_info, .pInputAssemblyState = &assembly_info, .pViewportState = &viewport_info, .pRasterizationState = &rasterization_info, .pMultisampleState = &multisample_info, .pDepthStencilState = nullptr, .pColorBlendState = &color_blend_info, .pDynamicState = nullptr, .layout = m_pipeline_layout, .renderPass = m_render_pass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = -1, }; if(vkCreateGraphicsPipelines( m_context.device(), VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &m_pipeline ) != VK_SUCCESS) throw std::runtime_error("failed to create graphic pipeline"); } void VulkanTutorial::create_framebuffers() { m_framebuffers.resize(m_swapchain.image_count()); for(size_t index = 0; index < m_framebuffers.size(); index += 1) { VkImageView view = m_swapchain.image_view(index); VkFramebufferCreateInfo framebuffer_info { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = m_render_pass, .attachmentCount = 1, .pAttachments = &view, .width = m_swapchain.extent().width, .height = m_swapchain.extent().height, .layers = 1, }; if(vkCreateFramebuffer( m_context.device(), &framebuffer_info, nullptr, &m_framebuffers[index] ) != VK_SUCCESS) throw std::runtime_error("failed to create framebuffer"); } } void VulkanTutorial::create_command_pool() { if(m_command_pool != VK_NULL_HANDLE) return; VkCommandPoolCreateInfo pool_info { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = 0, .queueFamilyIndex = m_context.queue_family(GRAPHIC_QUEUE), }; if(vkCreateCommandPool( m_context.device(), &pool_info, nullptr, &m_command_pool ) != VK_SUCCESS) throw std::runtime_error("failed to create command pool"); } void VulkanTutorial::create_vertex_buffer() { if(m_vertex_buffer != VK_NULL_HANDLE) return; VkDeviceSize size = sizeof(vertices[0]) * vertices.size(); std::tie(m_vertex_buffer, m_vertex_buffer_memory) = m_context.create_buffer( size, (char*)vertices.data(), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_context.queue_family(GRAPHIC_QUEUE) ); } void VulkanTutorial::create_index_buffer() { if(m_index_buffer != VK_NULL_HANDLE) return; VkDeviceSize size = sizeof(indices[0]) * indices.size(); std::tie(m_index_buffer, m_index_buffer_memory) = m_context.create_buffer( size, (char*)indices.data(), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, m_context.queue_family(GRAPHIC_QUEUE) ); } void VulkanTutorial::create_uniform_buffer() { m_uniform_buffers.assign(m_swapchain.image_count(), VK_NULL_HANDLE); for(size_t index = 0; index < m_uniform_buffers.size(); index += 1) { VkBufferCreateInfo buffer_info { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = sizeof(Uniforms), .usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; if(vkCreateBuffer( m_context.device(), &buffer_info, nullptr, &m_uniform_buffers[index] ) != VK_SUCCESS) throw std::runtime_error("failed to create buffer"); } VkMemoryRequirements memory_requirements; vkGetBufferMemoryRequirements( m_context.device(), m_uniform_buffers[0], &memory_requirements ); m_uniform_buffer_memory = m_context.allocate_memory( memory_requirements.size * m_swapchain.image_count(), memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT ); m_uniform_buffer_offsets.resize(m_uniform_buffers.size()); for(size_t index = 0; index < m_uniform_buffers.size(); index += 1) { m_uniform_buffer_offsets[index] = index * memory_requirements.size; vkBindBufferMemory( m_context.device(), m_uniform_buffers[index], m_uniform_buffer_memory, m_uniform_buffer_offsets[index] ); } } void VulkanTutorial::create_descriptor_pool() { VkDescriptorPoolSize pool_size { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = uint32_t(m_swapchain.image_count()), }; VkDescriptorPoolCreateInfo pool_info { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets = uint32_t(m_swapchain.image_count()), .poolSizeCount = 1, .pPoolSizes = &pool_size, }; if(vkCreateDescriptorPool( m_context.device(), &pool_info, nullptr, &m_descriptor_pool ) != VK_SUCCESS) throw std::runtime_error("failed to create descriptor pool"); } void VulkanTutorial::create_descriptor_sets() { const std::vector layouts( m_swapchain.image_count(), m_descriptor_set_layout); VkDescriptorSetAllocateInfo alloc_info { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = m_descriptor_pool, .descriptorSetCount = uint32_t(layouts.size()), .pSetLayouts = layouts.data(), }; m_descriptor_sets.resize(m_swapchain.image_count()); if(vkAllocateDescriptorSets( m_context.device(), &alloc_info, m_descriptor_sets.data() ) != VK_SUCCESS) throw std::runtime_error("failed to allocate descriptor sets"); for(size_t index = 0; index < m_swapchain.image_count(); index += 1) { VkDescriptorBufferInfo buffer_info { .buffer = m_uniform_buffers[index], .offset = 0, .range = sizeof(Uniforms), }; VkWriteDescriptorSet write { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = m_descriptor_sets[index], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .pBufferInfo = &buffer_info, }; vkUpdateDescriptorSets( m_context.device(), 1, &write, 0, nullptr ); } } void VulkanTutorial::create_command_buffers() { m_command_buffers.resize(m_framebuffers.size()); VkCommandBufferAllocateInfo allocate_info { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = m_command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = uint32_t(m_command_buffers.size()), }; if(vkAllocateCommandBuffers( m_context.device(), &allocate_info, m_command_buffers.data() ) != VK_SUCCESS) throw std::runtime_error("failed to allocate command buffers"); for(size_t index = 0; index < m_command_buffers.size(); index += 1) { VkCommandBuffer command_buffer = m_command_buffers[index]; VkCommandBufferBeginInfo begin_info { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = 0, .pInheritanceInfo = nullptr, }; logger.debug() << "record command buffer " << index << " (" << command_buffer << ")"; if(vkBeginCommandBuffer( command_buffer, &begin_info ) != VK_SUCCESS) throw std::runtime_error("failed to begin command buffer"); VkClearValue clear_color = { .color = { .float32 = { 0.0f, 0.0f, 0.0f, 1.0f } } }; VkRenderPassBeginInfo pass_info { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = m_render_pass, .framebuffer = m_framebuffers[index], .renderArea = { .offset = { 0, 0 }, .extent = m_swapchain.extent(), }, .clearValueCount = 1, .pClearValues = &clear_color, }; vkCmdBeginRenderPass( command_buffer, &pass_info, VK_SUBPASS_CONTENTS_INLINE ); vkCmdBindPipeline( command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline ); VkBuffer vertex_buffers[] = { m_vertex_buffer, }; VkDeviceSize offsets[] = { 0, }; vkCmdBindVertexBuffers( command_buffer, 0, 1, vertex_buffers, offsets ); vkCmdBindDescriptorSets( command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1, &m_descriptor_sets[index], 0, nullptr ); vkCmdBindIndexBuffer( command_buffer, m_index_buffer, 0, VK_INDEX_TYPE_UINT16 ); vkCmdDrawIndexed( command_buffer, uint32_t(indices.size()), 1, 0, 0, 0 ); vkCmdEndRenderPass(command_buffer); if(vkEndCommandBuffer(command_buffer) != VK_SUCCESS) throw("failed to record command buffer"); } }