diff options
| -rw-r--r-- | src/engine.cpp | 243 | ||||
| -rw-r--r-- | src/shaders/shader.slang | 34 | ||||
| -rw-r--r-- | src/vulkan_utils.hpp | 42 |
3 files changed, 289 insertions, 30 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); diff --git a/src/shaders/shader.slang b/src/shaders/shader.slang index b5f7a54..37041c6 100644 --- a/src/shaders/shader.slang +++ b/src/shaders/shader.slang @@ -1,30 +1,22 @@ -static float2 positions[3] = float2[] ( - float2( 0.0, -0.5), - float2( 0.5, 0.5), - float2(-0.5, 0.5), -); - -static float3 colors[3] = float3[] ( - float3(1.0, 0.0, 0.0), - float3(0.0, 1.0, 0.0), - float3(0.0, 0.0, 1.0), -); +struct VertexInput { + float2 pos; + float3 col; +}; struct VertexOutput { - float3 color; - float4 sv_position : SV_Position; + float4 pos : SV_Position; + float3 col; }; [shader("vertex")] -VertexOutput vert_main(uint vid : SV_VertexID) { - VertexOutput output; - output.color = colors[vid]; - output.sv_position = float4(positions[vid], 0.0, 1.0); - return output; +VertexOutput vert_main(VertexInput vi) { + VertexOutput vo; + vo.pos = float4(vi.pos, 0., 1.); + vo.col = vi.col; + return vo; } [shader("fragment")] -float4 frag_main(VertexOutput vert) : SV_Target { - float3 color = vert.color; - return float4(color, 1.0); +float4 frag_main(VertexOutput vo) : SV_Target { + return float4(vo.col, 1.); } diff --git a/src/vulkan_utils.hpp b/src/vulkan_utils.hpp index 8e6f617..dd57afc 100644 --- a/src/vulkan_utils.hpp +++ b/src/vulkan_utils.hpp @@ -1,12 +1,17 @@ #ifndef VULKAN_UTILS_HPP #define VULKAN_UTILS_HPP +#include <cstddef> +#include <cstdint> #include <ostream> // I don't know if we should directly include vulkan or not #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> -namespace engine::myvk { +#include "math/vector.hpp" +#include "o3d/vertex.hpp" + +namespace engine::vk { struct api { uint32_t raw; }; @@ -19,6 +24,41 @@ constexpr std::ostream& operator<<(std::ostream& os, const api& api) { << VK_API_VERSION_PATCH (api.raw); } +// TODO: we shouldn't have a struct specifically for vulkan vertices, it should be shared with the +// software renderer. But right now, they don't share the same infos, so that would make everything +// more complicated. Additionnaly, the way engine::o3d::Vertex is implemented right now locks +// precisely what can be send to the GPU, which is bad. Maybe we shouldn't have a general class like +// that, and lots of small classes for each specific case +struct Vertex { + static constexpr VkVertexInputBindingDescription get_binding_desc() { + return { + .binding = 0, + .stride = sizeof(Vertex), + .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, + }; + } + + static constexpr std::array<VkVertexInputAttributeDescription, 2> get_attr_descs() { + return { + VkVertexInputAttributeDescription { + .location = 0, + .binding = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof(Vertex, pos), + }, + VkVertexInputAttributeDescription { + .location = 1, + .binding = 0, + .format = VK_FORMAT_R32G32B32_SFLOAT, + .offset = offsetof(Vertex, col), + }, + }; + } + + engine::math::Vector2 pos; + engine::math::Vector3 col; +}; + } #endif // VULKAN_UTILS_HPP |
