diff options
| author | vimene <vincent.menegaux@gmail.com> | 2025-12-22 23:09:30 +0100 |
|---|---|---|
| committer | vimene <vincent.menegaux@gmail.com> | 2025-12-22 23:09:30 +0100 |
| commit | 236b6a3160cd3d8c217a966aaa2795c779c13a91 (patch) | |
| tree | c11909bd95668a105180d3926333eb081dabee72 /src/engine.cpp | |
| parent | a90d440e5e02712a9718d3cb30fcc31687439893 (diff) | |
| download | engine-236b6a3160cd3d8c217a966aaa2795c779c13a91.tar.gz | |
added {vertex,index} buffers
Diffstat (limited to 'src/engine.cpp')
| -rw-r--r-- | src/engine.cpp | 243 |
1 files changed, 235 insertions, 8 deletions
diff --git a/src/engine.cpp b/src/engine.cpp index 582eb1d..84e62a5 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -409,6 +409,130 @@ static std::tuple<std::optional<uint32_t>, std::optional<uint32_t>> find_queue_f return { graphics_queue_family_index, present_queue_family_index }; } +static uint32_t find_mem_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags flags) { + VkPhysicalDeviceMemoryProperties2 props {}; + props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2; + vkGetPhysicalDeviceMemoryProperties2(physical_device, &props); + for (uint32_t i = 0; i < props.memoryProperties.memoryTypeCount; i++) + if (type_filter & (1 << i) && (props.memoryProperties.memoryTypes[i].propertyFlags & flags) == flags) + return i; + + // not found + // TODO: improve by being explicit about what are requirements + std::cerr << "cannot find memory type matching requirements" << std::endl; + std::exit(EXIT_FAILURE); +} + +static std::tuple<VkBuffer, VkDeviceMemory> +create_buf(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags mem_flags) { + auto buf = [&]() { + VkBufferCreateInfo buf_ci { + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = {}, + .size = size, + .usage = usage, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = {}, + .pQueueFamilyIndices = {}, + }; + VkBuffer buf; + if (VkResult res = vkCreateBuffer(device, &buf_ci, nullptr, &buf); res != VK_SUCCESS) { + std::cerr << "failed to create buffer, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + return buf; + }(); + + VkMemoryRequirements buf_mem_requirements; + vkGetBufferMemoryRequirements(device, buf, &buf_mem_requirements); + + auto buf_device_mem = [&]() { + VkMemoryAllocateInfo buf_mem_ai { + .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, + .pNext = nullptr, + .allocationSize = buf_mem_requirements.size, + .memoryTypeIndex = find_mem_type(physical_device, buf_mem_requirements.memoryTypeBits, mem_flags), + }; + VkDeviceMemory buf_device_mem; + if (VkResult res = vkAllocateMemory(device, &buf_mem_ai, nullptr, &buf_device_mem); res != VK_SUCCESS) { + std::cerr << "failed to allocate buffer memory, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + return buf_device_mem; + }(); + + if (VkResult res = vkBindBufferMemory(device, buf, buf_device_mem, 0); res != VK_SUCCESS) { + std::cerr << "failed to bind buffer memory, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + return { buf, buf_device_mem }; +} + +static void copy_bufs(VkQueue graphics_queue, VkDevice device, VkCommandPool cmd_pool, VkBuffer src, VkBuffer dst, VkDeviceSize size) { + auto cmd_buf = [&]() { + VkCommandBufferAllocateInfo cmd_buf_ai { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .pNext = nullptr, + .commandPool = cmd_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + VkCommandBuffer cmd_buf; + if (VkResult res = vkAllocateCommandBuffers(device, &cmd_buf_ai, &cmd_buf); res != VK_SUCCESS) { + std::cerr << "failed to allocate copy command buffer, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + return cmd_buf; + }(); + { + VkCommandBufferBeginInfo cmd_buf_bi { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .pNext = nullptr, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + .pInheritanceInfo = {}, + }; + if (VkResult res = vkBeginCommandBuffer(cmd_buf, &cmd_buf_bi); res != VK_SUCCESS) { + std::cerr << "failed to begin copy command buffer, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + } + { + VkBufferCopy buf_copy { + .srcOffset = 0, + .dstOffset = 0, + .size = size, + }; + vkCmdCopyBuffer(cmd_buf, src, dst, 1, &buf_copy); + } + if (VkResult res = vkEndCommandBuffer(cmd_buf); res != VK_SUCCESS) { + std::cerr << "failed to end copy command buffer, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + { + VkSubmitInfo submit_info { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .pNext = nullptr, + .waitSemaphoreCount = {}, + .pWaitSemaphores = {}, + .pWaitDstStageMask = {}, + .commandBufferCount = 1, + .pCommandBuffers = &cmd_buf, + .signalSemaphoreCount = {}, + .pSignalSemaphores = {}, + }; + if (VkResult res = vkQueueSubmit(graphics_queue, 1, &submit_info, nullptr); res != VK_SUCCESS) { + std::cerr << "failed to submit copy command buffer to queue, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + } + if (VkResult res = vkQueueWaitIdle(graphics_queue); res != VK_SUCCESS) { + std::cerr << "failed to wait idle for graphics queue, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + vkFreeCommandBuffers(device, cmd_pool, 1, &cmd_buf); +} + static void transition_image_layout(VkCommandBuffer cmd_buf, VkImage img, VkImageLayout old_layout, VkImageLayout new_layout, VkAccessFlags2 src_access_mask, VkAccessFlags2 dst_access_mask, @@ -464,8 +588,19 @@ static int main_graphical() { *reinterpret_cast<bool*>(glfwGetWindowUserPointer(window)) = true; }); + // TODO: verify alignment requirements + // TODO: maybe move these definitions? + const std::array vertices { + engine::vk::Vertex { { -.5f, -.5f }, { 1.f, 0.f, 0.f } }, + engine::vk::Vertex { { .5f, -.5f }, { 0.f, 1.f, 0.f } }, + engine::vk::Vertex { { .5f, .5f }, { 0.f, 0.f, 1.f } }, + engine::vk::Vertex { { -.5f, .5f }, { 1.f, 1.f, 1.f } }, + }; + + const std::array<uint16_t, 6> indices { 0, 1, 2, 2, 3, 0 }; + // init Vulkan - std::cout << "Vulkan loader version: " << engine::myvk::api { VK_HEADER_VERSION_COMPLETE } << std::endl; + std::cout << "Vulkan loader version: " << engine::vk::api { VK_HEADER_VERSION_COMPLETE } << std::endl; // init Vulkan - create instance if (enable_validation_layers && !check_validation_layer_support()) { @@ -604,7 +739,7 @@ static int main_graphical() { }(); std::cout << " " << (i + 1) << ". " << physical_device_props.properties.deviceName << ":" << std::endl; - std::cout << " apiVersion: " << engine::myvk::api { physical_device_props.properties.apiVersion } << std::endl; + std::cout << " apiVersion: " << engine::vk::api { physical_device_props.properties.apiVersion } << std::endl; auto physical_device_features = [&]() { VkPhysicalDeviceFeatures2 physical_device_features{}; @@ -1074,14 +1209,17 @@ static int main_graphical() { .pDynamicStates = dynamic_states.data(), }; + const auto vertex_binding_desc = engine::vk::Vertex::get_binding_desc(); + const auto vertex_attr_descs = engine::vk::Vertex::get_attr_descs(); + VkPipelineVertexInputStateCreateInfo pl_vert_in_state_ci { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .pNext = nullptr, .flags = {}, - .vertexBindingDescriptionCount = 0, - .pVertexBindingDescriptions = nullptr, - .vertexAttributeDescriptionCount = 0, - .pVertexAttributeDescriptions = nullptr, + .vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &vertex_binding_desc, + .vertexAttributeDescriptionCount = static_cast<uint32_t>(vertex_attr_descs.size()), + .pVertexAttributeDescriptions = vertex_attr_descs.data(), }; VkPipelineInputAssemblyStateCreateInfo pl_in_asm_state_ci { @@ -1235,6 +1373,80 @@ static int main_graphical() { return cmd_pool; }(); + // create vertex buffer + auto [vertex_buf, vertex_buf_device_mem] = [&]() { + VkDeviceSize vertex_buf_size = sizeof(engine::vk::Vertex) * vertices.size(); + + auto [staging_buf, staging_buf_device_mem] = create_buf(physical_device, device, vertex_buf_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + { + void* staging_buf_mem; + + if (VkResult res = vkMapMemory(device, staging_buf_device_mem, 0, vertex_buf_size, 0, &staging_buf_mem); res != VK_SUCCESS) { + std::cerr << "failed to map staging buffer memory, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + + memcpy(staging_buf_mem, vertices.data(), static_cast<size_t>(vertex_buf_size)); + + vkUnmapMemory(device, staging_buf_device_mem); + } + + auto [vertex_buf, vertex_buf_device_mem] = create_buf(physical_device, device, vertex_buf_size, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + // TODO: consider using different command pool for short-lived command buffers, with the flag + // VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, for optimization. For now we will stick to the one + // which does the rendering + // TODO: don't wait individually for every copy operations, use a fence or something similar + copy_bufs(graphics_queue, device, cmd_pool, staging_buf, vertex_buf, vertex_buf_size); + + vkFreeMemory(device, staging_buf_device_mem, nullptr); + vkDestroyBuffer(device, staging_buf, nullptr); + + return std::tuple { vertex_buf, vertex_buf_device_mem }; + }(); + + // create index buffer + // TODO: this code is pretty much a duplicate with minor differences of the vertex_buf one. We + // should probably factor it out in a function. Also, every TODOs were remove for this one, but + // they still hold + auto [index_buf, index_buf_device_mem] = [&]() { + VkDeviceSize index_buf_size = sizeof(uint16_t) * indices.size(); + + auto [staging_buf, staging_buf_device_mem] = create_buf(physical_device, device, index_buf_size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + + { + void* staging_buf_mem; + + if (VkResult res = vkMapMemory(device, staging_buf_device_mem, 0, index_buf_size, 0, &staging_buf_mem); res != VK_SUCCESS) { + std::cerr << "failed to map staging buffer memory, error code: " << string_VkResult(res) << std::endl; + std::exit(EXIT_FAILURE); + } + + memcpy(staging_buf_mem, indices.data(), static_cast<size_t>(index_buf_size)); + + vkUnmapMemory(device, staging_buf_device_mem); + } + + auto [index_buf, index_buf_device_mem] = create_buf(physical_device, device, index_buf_size, + VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + + copy_bufs(graphics_queue, device, cmd_pool, staging_buf, index_buf, index_buf_size); + + vkFreeMemory(device, staging_buf_device_mem, nullptr); + vkDestroyBuffer(device, staging_buf, nullptr); + + return std::tuple { index_buf, index_buf_device_mem }; + }(); + + // create command buffers auto cmd_bufs = [&]() { VkCommandBufferAllocateInfo cmd_buf_ai { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, @@ -1378,6 +1590,12 @@ static int main_graphical() { VkCommandBufferBeginInfo cmd_buf_bi { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .pNext = nullptr, + // TODO: I don't understand why here we shouldn't also set + // VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, because we always submit this + // command buffer once before resetting it, like when we copy the staging buffer to + // the vertex buffer (where we set this flag). The only difference is that we wait + // for the queue to idle for the latter, where here we only fence for the end of + // vkQueueSubmit .flags = {}, .pInheritanceInfo = {}, }; @@ -1443,7 +1661,11 @@ static int main_graphical() { vkCmdSetScissor(cmd_bufs[frame_idx], 0, 1, &scissor); } - vkCmdDraw(cmd_bufs[frame_idx], 3, 1, 0, 0); + VkDeviceSize vertex_buf_offset = 0; + vkCmdBindVertexBuffers(cmd_bufs[frame_idx], 0, 1, &vertex_buf, &vertex_buf_offset); + + vkCmdBindIndexBuffer(cmd_bufs[frame_idx], index_buf, 0, VK_INDEX_TYPE_UINT16); + vkCmdDrawIndexed(cmd_bufs[frame_idx], static_cast<uint32_t>(indices.size()), 1, 0, 0, 0); vkCmdEndRendering(cmd_bufs[frame_idx]); @@ -1476,7 +1698,7 @@ static int main_graphical() { .pSignalSemaphores = &sems_render_finished[img_idx], }; if (VkResult res = vkQueueSubmit(graphics_queue, 1, &submit_info, fences_in_flight[frame_idx]); res != VK_SUCCESS) { - std::cerr << "failed to submit queue, error code: " << string_VkResult(res) << std::endl; + std::cerr << "failed to submit command buffer queue, error code: " << string_VkResult(res) << std::endl; std::exit(EXIT_FAILURE); } } @@ -1520,6 +1742,11 @@ static int main_graphical() { vkDestroySemaphore(device, *it_sem, nullptr); for (auto it_sem = sems_present_complete.rbegin(); it_sem != sems_present_complete.rend(); ++it_sem) vkDestroySemaphore(device, *it_sem, nullptr); + vkFreeMemory(device, index_buf_device_mem, nullptr); + vkDestroyBuffer(device, index_buf, nullptr); + vkFreeMemory(device, vertex_buf_device_mem, nullptr); + vkDestroyBuffer(device, vertex_buf, nullptr); + // no need to free cmd_buf because destroying the command pool will vkDestroyCommandPool(device, cmd_pool, nullptr); vkDestroyPipeline(device, graphics_pl, nullptr); vkDestroyPipelineLayout(device, pl_layout, nullptr); |
