aboutsummaryrefslogtreecommitdiff
path: root/src/engine.cpp
diff options
context:
space:
mode:
authorvimene <vincent.menegaux@gmail.com>2025-12-22 23:09:30 +0100
committervimene <vincent.menegaux@gmail.com>2025-12-22 23:09:30 +0100
commit236b6a3160cd3d8c217a966aaa2795c779c13a91 (patch)
treec11909bd95668a105180d3926333eb081dabee72 /src/engine.cpp
parenta90d440e5e02712a9718d3cb30fcc31687439893 (diff)
downloadengine-236b6a3160cd3d8c217a966aaa2795c779c13a91.tar.gz
added {vertex,index} buffers
Diffstat (limited to 'src/engine.cpp')
-rw-r--r--src/engine.cpp243
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);