diff --git a/.gitignore b/.gitignore index 509afee5..2961ccac 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ latex html build glslc +glm diff --git a/CMakeLists.txt b/CMakeLists.txt index 59f1ac7a..a4bc984e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ target_sources(${PROJECT_NAME} src/app/threads/AThreadWrapper.cpp src/app/threads/render/RenderThread.cpp src/app/threads/update/UpdateThread.cpp + src/app/vulkan/VulkanAPI.cpp src/app/window/window.cpp src/app/window/input.cpp ) @@ -47,12 +48,9 @@ target_include_directories(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/src/app/threads/render ${CMAKE_CURRENT_SOURCE_DIR}/src/app/threads/update ${CMAKE_CURRENT_SOURCE_DIR}/src/app/threads/block_update + ${CMAKE_CURRENT_SOURCE_DIR}/src/app/vulkan ${CMAKE_CURRENT_SOURCE_DIR}/src/app/window ) -# link libraries -add_subdirectory(external/cppVulkanAPI) -target_link_libraries(${PROJECT_NAME} - PRIVATE - cppVulkanAPI -) +target_link_libraries(${PROJECT_NAME} glfw vulkan dl pthread X11 Xxf86vm Xrandr Xi) + diff --git a/build.sh b/build.sh index 11199109..760184c2 100755 --- a/build.sh +++ b/build.sh @@ -2,8 +2,8 @@ # Sript to build the project -# Download the necessary external libraries -./scripts/download_cppVulkanAPI.sh +# Download dependencies +./scripts/download_glm.sh # Compile shaders ./scripts/compile_shaders.sh diff --git a/scripts/download_cppVulkanAPI.sh b/scripts/download_cppVulkanAPI.sh deleted file mode 100755 index 1e316c2d..00000000 --- a/scripts/download_cppVulkanAPI.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -if [ ! -d external/cppVulkanAPI ] -then - echo "cloning 'git@github.com:SaumonDesMers/cppVulkanAPI.git' in external/cppVulkanAPI" - - git clone git@github.com:SaumonDesMers/cppVulkanAPI.git external/cppVulkanAPI -fi \ No newline at end of file diff --git a/scripts/download_glm.sh b/scripts/download_glm.sh new file mode 100755 index 00000000..fd4fff91 --- /dev/null +++ b/scripts/download_glm.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [ ! -d external/glm ] +then + echo "cloning 'https://github.com/g-truc/glm.git' in external/glm" + + mkdir -p external/glm + git clone https://github.com/g-truc/glm.git external/glm +fi \ No newline at end of file diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index f7cfa452..13009da8 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -1,19 +1,9 @@ #version 450 -layout(set=1, binding = 0) uniform sampler2D texSampler; - -layout(location = 0) in vec3 fragNormal; -layout(location = 1) in vec2 fragTexCoord; +layout(location = 0) in vec3 fragColor; layout(location = 0) out vec4 outColor; -layout(location = 1) out vec3 outNormal; void main() { - outColor = texture(texSampler, fragTexCoord); - - vec3 abs_normal = abs(fragNormal); - - float grey_shade = abs_normal.x * 0.299 + abs_normal.y * 0.587 + abs_normal.z * 0.114; - - outNormal = vec3(grey_shade, grey_shade, grey_shade); -} + outColor = vec4(fragColor, 1.0); +} \ No newline at end of file diff --git a/shaders/simple_shader.vert b/shaders/simple_shader.vert index 79d1acd1..66d67662 100644 --- a/shaders/simple_shader.vert +++ b/shaders/simple_shader.vert @@ -1,26 +1,20 @@ #version 450 -layout(set=0, binding = 0) uniform UniformBufferObject { - mat4 view; - mat4 proj; -} ubo; +layout(location = 0) out vec3 fragColor; -layout(push_constant) uniform PushConstantObject { - mat4 model; -} pc; +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec2 inTexCoord; - -layout(location = 0) out vec3 fragNormal; -layout(location = 1) out vec2 fragTexCoord; +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); void main() { - gl_Position = ubo.proj * ubo.view * pc.model * vec4(inPosition, 1.0); - - // convert normal to world space - fragNormal = normalize((pc.model * vec4(inNormal, 0.0)).xyz); - - fragTexCoord = inTexCoord; -} + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} \ No newline at end of file diff --git a/src/app/application.cpp b/src/app/application.cpp index 6e3f7eb0..3e323e60 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -8,8 +8,8 @@ Application::Application(): m_settings(), m_world_scene(), m_window("Vox", 800, 600), - m_renderAPI(m_window.getGLFWwindow()), - m_render_thread(m_settings, m_renderAPI, m_world_scene), + m_vulkan_api(m_window.getGLFWwindow()), + m_render_thread(m_settings, m_vulkan_api, m_world_scene), m_update_thread(m_settings, m_window, m_world_scene, m_start_time) { LOG_INFO("Application::Application()"); diff --git a/src/app/application.hpp b/src/app/application.hpp index 4354ef9d..28c220b9 100644 --- a/src/app/application.hpp +++ b/src/app/application.hpp @@ -6,8 +6,7 @@ #include "RenderThread.hpp" #include "WorldScene.hpp" #include "UpdateThread.hpp" - -#include +#include "VulkanAPI.hpp" #include @@ -28,7 +27,7 @@ class Application Settings m_settings; WorldScene m_world_scene; Window m_window; - vk::RenderAPI m_renderAPI; + VulkanAPI m_vulkan_api; RenderThread m_render_thread; UpdateThread m_update_thread; }; diff --git a/src/app/scenes/WorldScene.hpp b/src/app/scenes/WorldScene.hpp index 5b6c8a04..5f1e921c 100644 --- a/src/app/scenes/WorldScene.hpp +++ b/src/app/scenes/WorldScene.hpp @@ -3,7 +3,8 @@ #include "define.hpp" #include "logger.hpp" -#include +#include +#include #include #include diff --git a/src/app/threads/render/RenderThread.cpp b/src/app/threads/render/RenderThread.cpp index 8a34faf4..9c0c022d 100644 --- a/src/app/threads/render/RenderThread.cpp +++ b/src/app/threads/render/RenderThread.cpp @@ -5,12 +5,12 @@ RenderThread::RenderThread( const Settings & settings, - vk::RenderAPI & renderAPI, + VulkanAPI & vulkanAPI, const WorldScene & worldScene ): AThreadWrapper(), m_settings(settings), - m_renderAPI(renderAPI), + vk(vulkanAPI), m_world_scene(worldScene) { } @@ -21,124 +21,306 @@ RenderThread::~RenderThread() void RenderThread::init() { - uint64_t mesh_id = m_renderAPI.loadModel("assets/models/cube.obj"); - LOG_DEBUG("Mesh ID: " << mesh_id); +} +void RenderThread::loop() +{ + static auto startTime = std::chrono::high_resolution_clock::now(); - vk::UniformBuffer::CreateInfo uniform_buffer_info{}; - uniform_buffer_info.size = sizeof(ViewProj_UBO); - uniform_buffer_info.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + auto currentTime = std::chrono::high_resolution_clock::now(); + auto time = std::chrono::duration(currentTime - startTime).count(); + (void)time; - m_proj_view_ubo_id = m_renderAPI.newUniformBuffer(uniform_buffer_info); - vk::Texture::CreateInfo texture_info{}; - texture_info.filepath = "assets/textures/grass.jpg"; - texture_info.mipLevel = 1; - texture_info.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + vkWaitForFences(vk.device, 1, &vk.in_flight_fences[vk.current_frame], VK_TRUE, std::numeric_limits::max()); + vkResetFences(vk.device, 1, &vk.in_flight_fences[vk.current_frame]); - m_texture_id = m_renderAPI.loadTexture(texture_info); + vkResetCommandBuffer(vk.render_command_buffers[vk.current_frame], 0); + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - m_texture_color_target_id = m_renderAPI.newColorTarget(); - m_normal_color_target_id = m_renderAPI.newColorTarget(); - m_depth_target_id = m_renderAPI.newDepthTarget(); + VK_CHECK( + vkBeginCommandBuffer(vk.render_command_buffers[vk.current_frame], &begin_info), + "Failed to begin recording command buffer!" + ); + std::array color_attachments = {}; + color_attachments[0].sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO; + color_attachments[0].imageView = vk.color_attachement_view; + color_attachments[0].imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + color_attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; + color_attachments[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE; + color_attachments[0].clearValue = { 0.0f, 0.0f, 0.0f, 1.0f }; - vk::Pipeline::CreateInfo pipeline_create_info{}; - pipeline_create_info.vertex_shader_path = "shaders/simple_shader.vert.spv"; - pipeline_create_info.fragment_shader_path = "shaders/simple_shader.frag.spv"; - pipeline_create_info.descriptor_set_layouts = { - m_renderAPI.getUniformBuffer(m_proj_view_ubo_id).descriptor()->layout(), - m_renderAPI.getTexture(m_texture_id).descriptor()->layout() - }; - pipeline_create_info.push_constant_ranges = { - {VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ModelMatrix_push_constant)} - }; - pipeline_create_info.color_target_ids = { - m_texture_color_target_id, - m_normal_color_target_id - }; - pipeline_create_info.depth_target_id = m_depth_target_id; + VkRenderingInfo render_info = {}; + render_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO; + render_info.renderArea = { 0, 0, vk.color_attachement_extent.width, vk.color_attachement_extent.height }; + render_info.layerCount = 1; + render_info.colorAttachmentCount = static_cast(color_attachments.size()); + render_info.pColorAttachments = color_attachments.data(); - m_simple_shader_pipeline_id = m_renderAPI.newPipeline(pipeline_create_info); -} + vkCmdBeginRendering(vk.render_command_buffers[vk.current_frame], &render_info); -void RenderThread::loop() -{ - static auto startTime = std::chrono::high_resolution_clock::now(); - auto currentTime = std::chrono::high_resolution_clock::now(); - auto time = std::chrono::duration(currentTime - startTime).count(); - (void)time; + vkCmdBindPipeline(vk.render_command_buffers[vk.current_frame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk.graphics_pipeline); + + vkCmdDraw(vk.render_command_buffers[vk.current_frame], 3, 1, 0, 0); + + + vkCmdEndRendering(vk.render_command_buffers[vk.current_frame]); + + //############################################################################################################ + // # + // Auguste tu commence ici # + // # + //############################################################################################################ + + /* + * vk.width() = the width of the image + * vk.height() = the height of the image + * vk.clearPixels() = clear the draw image + * vk.putPixel(x, y, r, g, b, a = 255) = put a pixel at the position (x, y) with the color (r, g, b, a) + */ + + vk.clearPixels(); + + // Draw a green rectangle + for (uint32_t x = vk.width() / 4; x < vk.width() / 2; x++) + { + for (uint32_t y = vk.height() / 4; y < vk.height() / 2; y++) + { + vk.putPixel(x, y, 0, 255, 0); + } + } + + // Draw a red rectangle + for (uint32_t x = 100; x < 200; x++) + { + for (uint32_t y = 100; y < 200; y++) + { + vk.putPixel(x, y, 255, 0, 0); + } + } + + //############################################################################################################ + // # + // Et tu t'arrĂȘtes la :) # + // # + //############################################################################################################ + - std::vector mesh_render_data = m_world_scene.getMeshRenderData(); + //############################################################################################################ + // # + // Submit the command buffer to the graphics queue # + // # + //############################################################################################################ - m_renderAPI.startDraw(); - m_renderAPI.startRendering( - {m_texture_color_target_id, m_normal_color_target_id}, - m_depth_target_id + VK_CHECK( + vkEndCommandBuffer(vk.render_command_buffers[vk.current_frame]), + "Failed to record command buffer" ); - //############################################################################ + VkSubmitInfo render_submit_info = {}; + render_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + render_submit_info.commandBufferCount = 1; + render_submit_info.pCommandBuffers = &vk.render_command_buffers[vk.current_frame]; + render_submit_info.signalSemaphoreCount = 1; + render_submit_info.pSignalSemaphores = &vk.render_finished_semaphores[vk.current_frame]; - int width, height; - glfwGetFramebufferSize(m_renderAPI.getWindow(), &width, &height); + VK_CHECK( + vkQueueSubmit(vk.graphics_queue, 1, &render_submit_info, vk.in_flight_fences[vk.current_frame]), + "Failed to submit draw command buffer" + ); + + //############################################################################################################ + // # + // Copy the color image attachment to the swap chain image with blit # + // # + //############################################################################################################ + + // Acquire the next swap chain image + uint32_t image_index; + VkResult result = vkAcquireNextImageKHR( + vk.device, + vk.swap_chain, + std::numeric_limits::max(), + vk.image_available_semaphores[vk.current_frame], + VK_NULL_HANDLE, + &image_index + ); + + if (result == VK_ERROR_OUT_OF_DATE_KHR) + { + vk.recreateSwapChain(vk.window); + return; + } + else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) + { + throw std::runtime_error("Failed to acquire swap chain image"); + } + + + // Transition the swap chain image from present to transfer destination + vk.transitionImageLayout( + vk.swap_chain_images[image_index], + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 0, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT + ); + + // Transition the color image from color attachment to transfer source + vk.transitionImageLayout( + vk.color_attachement_image, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 0, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT + ); + + // Copy the color image to the swap chain image with blit + vkResetCommandBuffer(vk.copy_command_buffers[vk.current_frame], 0); + + VkCommandBufferBeginInfo copy_begin_info = {}; + copy_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + + VK_CHECK( + vkBeginCommandBuffer(vk.copy_command_buffers[vk.current_frame], ©_begin_info), + "Failed to begin recording copy command buffer" + ); + + VkImageBlit blit = {}; + blit.srcOffsets[0] = { 0, 0, 0 }; + blit.srcOffsets[1] = { + // static_cast(vk.color_attachement_extent.width), + // static_cast(vk.color_attachement_extent.height), + static_cast(vk.draw_image_extent.width), + static_cast(vk.draw_image_extent.height), + 1 + }; + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = 0; + blit.srcSubresource.baseArrayLayer = 0; + blit.srcSubresource.layerCount = 1; + blit.dstOffsets[0] = { 0, 0, 0 }; + blit.dstOffsets[1] = { + static_cast(vk.swap_chain_extent.width), + static_cast(vk.swap_chain_extent.height), + 1 + }; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = 0; + blit.dstSubresource.baseArrayLayer = 0; + blit.dstSubresource.layerCount = 1; + + vkCmdBlitImage( + vk.copy_command_buffers[vk.current_frame], + // vk.color_attachement_image, + // VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + vk.draw_image, + VK_IMAGE_LAYOUT_GENERAL, + vk.swap_chain_images[image_index], + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &blit, + VK_FILTER_LINEAR + ); + + VK_CHECK( + vkEndCommandBuffer(vk.copy_command_buffers[vk.current_frame]), + "Failed to record copy command buffer" + ); + + VkSubmitInfo copy_submit_info = {}; + copy_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + copy_submit_info.commandBufferCount = 1; + copy_submit_info.pCommandBuffers = &vk.copy_command_buffers[vk.current_frame]; - ViewProj_UBO ubo{}; - ubo.view = m_world_scene.camera().getViewMatrix(); - ubo.proj = m_world_scene.camera().getProjectionMatrix(width / (float) height); - ubo.proj[1][1] *= -1; + std::array copy_wait_semaphores = { + vk.image_available_semaphores[vk.current_frame], + vk.render_finished_semaphores[vk.current_frame] + }; + std::array copy_wait_stages = { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT + }; + copy_submit_info.waitSemaphoreCount = static_cast(copy_wait_semaphores.size()); + copy_submit_info.pWaitSemaphores = copy_wait_semaphores.data(); + copy_submit_info.pWaitDstStageMask = copy_wait_stages.data(); - m_renderAPI.getUniformBuffer(m_proj_view_ubo_id).buffer(m_renderAPI.currentFrame())->write(&ubo, sizeof(ubo)); + copy_submit_info.signalSemaphoreCount = 1; + copy_submit_info.pSignalSemaphores = &vk.swap_chain_updated_semaphores[vk.current_frame]; + + VK_CHECK( + vkQueueSubmit(vk.graphics_queue, 1, ©_submit_info, VK_NULL_HANDLE), + "Failed to submit copy command buffer" + ); - m_renderAPI.bindPipeline(m_simple_shader_pipeline_id); + VK_CHECK( + vkQueueWaitIdle(vk.graphics_queue), + "Failed to wait for queue to become idle" + ); - m_renderAPI.bindDescriptor( - m_simple_shader_pipeline_id, - 0, 1, - m_renderAPI.getUniformBuffer(m_proj_view_ubo_id).descriptor()->pSet(m_renderAPI.currentFrame()) + // Transition the swap chain image from transfer destination to present + vk.transitionImageLayout( + vk.swap_chain_images[image_index], + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 0, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT ); - m_renderAPI.bindDescriptor( - m_simple_shader_pipeline_id, - 1, 1, - m_renderAPI.getTexture(m_texture_id).descriptor()->pSet(m_renderAPI.currentFrame()) + // Transition the color image from transfer source to color attachment + vk.transitionImageLayout( + vk.color_attachement_image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 0, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT ); + //############################################################################################################ + // # + // Present the swap chain image to the present queue # + // # + //############################################################################################################ - VkViewport viewport{}; - viewport.x = 0.0f; - viewport.y = 0.0f; - viewport.width = static_cast(width); - viewport.height = static_cast(height); - viewport.minDepth = 0.0f; - viewport.maxDepth = 1.0f; - m_renderAPI.setViewport(viewport); + VkPresentInfoKHR present_info = {}; + present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + present_info.waitSemaphoreCount = 1; + present_info.pWaitSemaphores = &vk.swap_chain_updated_semaphores[vk.current_frame]; + present_info.swapchainCount = 1; + present_info.pSwapchains = &vk.swap_chain; + present_info.pImageIndices = &image_index; - VkRect2D scissor{}; - scissor.offset = { 0, 0 }; - scissor.extent = { static_cast(width), static_cast(height) }; - m_renderAPI.setScissor(scissor); + result = vkQueuePresentKHR(vk.present_queue, &present_info); - for (auto& mesh_data : mesh_render_data) + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { - ModelMatrix_push_constant pushConstant{}; - pushConstant.model = mesh_data.transform.model() - * glm::rotate(glm::mat4(1.0f), time * glm::radians(45.0f), glm::vec3(0.0f, 1.0f, 0.0f)) - * glm::rotate(glm::mat4(1.0f), time * glm::radians(45.0f), glm::vec3(1.0f, 0.0f, 0.0f)); - m_renderAPI.pushConstant( - m_simple_shader_pipeline_id, - VK_SHADER_STAGE_VERTEX_BIT, - sizeof(ModelMatrix_push_constant), - &pushConstant - ); - - m_renderAPI.drawMesh(mesh_data.id); + vk.recreateSwapChain(vk.window); + } + else if (result != VK_SUCCESS) + { + throw std::runtime_error("Failed to present swap chain image"); } - //############################################################################ - - m_renderAPI.endRendering(); - // m_renderAPI.endDraw(m_texture_color_target_id); - m_renderAPI.endDraw(m_normal_color_target_id); + // Increment the current frame + vk.current_frame = (vk.current_frame + 1) % vk.max_frames_in_flight; } diff --git a/src/app/threads/render/RenderThread.hpp b/src/app/threads/render/RenderThread.hpp index 52a36df2..d4887ceb 100644 --- a/src/app/threads/render/RenderThread.hpp +++ b/src/app/threads/render/RenderThread.hpp @@ -4,8 +4,7 @@ #include "AThreadWrapper.hpp" #include "WorldScene.hpp" #include "Settings.hpp" - -#include +#include "VulkanAPI.hpp" /** * @brief The push constant for the model matrix. @@ -33,7 +32,7 @@ class RenderThread : public AThreadWrapper */ RenderThread( const Settings & settings, - vk::RenderAPI & renderAPI, + VulkanAPI & vulkanAPI, const WorldScene & worldScene ); @@ -50,16 +49,9 @@ class RenderThread : public AThreadWrapper private: const Settings & m_settings; - vk::RenderAPI & m_renderAPI; + VulkanAPI & vk; const WorldScene & m_world_scene; - uint64_t m_proj_view_ubo_id; - uint64_t m_texture_id; - uint64_t m_texture_color_target_id; - uint64_t m_normal_color_target_id; - uint64_t m_depth_target_id; - uint64_t m_simple_shader_pipeline_id; - /** * @brief function used to initialize the vulkan ressources via the renderAPI * diff --git a/src/app/vulkan/VulkanAPI.cpp b/src/app/vulkan/VulkanAPI.cpp new file mode 100644 index 00000000..169e7820 --- /dev/null +++ b/src/app/vulkan/VulkanAPI.cpp @@ -0,0 +1,1078 @@ +#include "VulkanAPI.hpp" +#include "logger.hpp" + +#include +#include + +VulkanAPI::VulkanAPI(GLFWwindow * window): + window(window) +{ + createInstance(); + setupDebugMessenger(); + createSurface(window); + pickPhysicalDevice(); + createLogicalDevice(); + createSwapChain(window); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); + + createColorResources(); + createPipeline(); + + createDrawImage(); + + LOG_INFO("VulkanAPI initialized"); +} + +VulkanAPI::~VulkanAPI() +{ + vkDeviceWaitIdle(device); + + vkDestroyImage(device, draw_image, nullptr); + vkUnmapMemory(device, draw_image_memory); + vkFreeMemory(device, draw_image_memory, nullptr); + + for (size_t i = 0; i < static_cast(max_frames_in_flight); i++) + { + vkDestroySemaphore(device, image_available_semaphores[i], nullptr); + vkDestroySemaphore(device, render_finished_semaphores[i], nullptr); + vkDestroySemaphore(device, swap_chain_updated_semaphores[i], nullptr); + vkDestroyFence(device, in_flight_fences[i], nullptr); + } + + vkDestroyImageView(device, color_attachement_view, nullptr); + vkFreeMemory(device, color_attachement_memory, nullptr); + vkDestroyImage(device, color_attachement_image, nullptr); + + vkFreeCommandBuffers(device, command_pool, static_cast(render_command_buffers.size()), render_command_buffers.data()); + vkFreeCommandBuffers(device, command_pool, static_cast(copy_command_buffers.size()), copy_command_buffers.data()); + vkDestroyCommandPool(device, command_pool, nullptr); + + vkDestroyPipeline(device, graphics_pipeline, nullptr); + vkDestroyPipelineLayout(device, pipeline_layout, nullptr); + + vkDestroySwapchainKHR(device, swap_chain, nullptr); + + vkDestroyDevice(device, nullptr); + + #ifndef NDEBUG + DestroyDebugUtilsMessengerEXT(instance, debug_messenger, nullptr); + #endif + + vkDestroySurfaceKHR(instance, surface, nullptr); + + vkDestroyInstance(instance, nullptr); + +} + +void VulkanAPI::createInstance() +{ + #ifndef NDEBUG + if (!checkValidationLayerSupport()) + { + throw std::runtime_error("Validation layers requested, but not available!"); + } + #endif + + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pApplicationName = "Vulkan Tutorial"; + app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + app_info.pEngineName = "No Engine"; + app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0); + app_info.apiVersion = VK_API_VERSION_1_3; + + VkInstanceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + create_info.pApplicationInfo = &app_info; + + std::vector extensions = getRequiredExtensions(); + + create_info.enabledExtensionCount = static_cast(extensions.size()); + create_info.ppEnabledExtensionNames = extensions.data(); + + VkDebugUtilsMessengerCreateInfoEXT debug_create_info; + populateDebugMessengerCreateInfo(debug_create_info); + #ifndef NDEBUG + create_info.enabledLayerCount = static_cast(validation_layers.size()); + create_info.ppEnabledLayerNames = validation_layers.data(); + + create_info.pNext = &debug_create_info; + #else + create_info.enabledLayerCount = 0; + #endif + + VK_CHECK( + vkCreateInstance(&create_info, nullptr, &instance), + "Failed to create instance" + ); +} + +bool VulkanAPI::checkValidationLayerSupport() +{ + uint32_t layer_count; + vkEnumerateInstanceLayerProperties(&layer_count, nullptr); + std::vector available_layers(layer_count); + vkEnumerateInstanceLayerProperties(&layer_count, available_layers.data()); + + for (const char * layer_name : validation_layers) + { + bool layer_found = false; + + for (const auto & layer_properties : available_layers) + { + if (strcmp(layer_name, layer_properties.layerName) == 0) + { + layer_found = true; + break; + } + } + + if (!layer_found) + { + return false; + } + } + + return true; +} + +std::vector VulkanAPI::getRequiredExtensions() +{ + uint32_t glfw_extension_count = 0; + const char** glfw_extensions; + glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count); + + std::vector extensions(glfw_extensions, glfw_extensions + glfw_extension_count); + + #ifndef NDEBUG + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + #endif + + return extensions; +} + +void VulkanAPI::setupDebugMessenger() +{ + #ifndef NDEBUG + VkDebugUtilsMessengerCreateInfoEXT create_info; + populateDebugMessengerCreateInfo(create_info); + + VK_CHECK( + CreateDebugUtilsMessengerEXT(instance, &create_info, nullptr, &debug_messenger), + "Failed to set up debug messenger" + ); + #endif +} + +void VulkanAPI::populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT & create_info) +{ + create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + create_info.pfnUserCallback = debugCallback; +} + +VkResult VulkanAPI::CreateDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT * create_info, + const VkAllocationCallbacks * allocator, + VkDebugUtilsMessengerEXT * debug_messenger +) +{ + auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if (func != nullptr) + { + return func(instance, create_info, allocator, debug_messenger); + } + else + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void VulkanAPI::DestroyDebugUtilsMessengerEXT( + VkInstance instance, + VkDebugUtilsMessengerEXT debug_messenger, + const VkAllocationCallbacks * allocator +) +{ + auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); + if (func != nullptr) + { + func(instance, debug_messenger, allocator); + } +} + +VKAPI_ATTR VkBool32 VKAPI_CALL VulkanAPI::debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_type, + const VkDebugUtilsMessengerCallbackDataEXT * callback_data, + void * +) +{ + (void)message_type; + + // if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) + // { + // LOG_TRACE(callback_data->pMessage); + // } + // if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) + // { + // LOG_INFO(callback_data->pMessage); + // } + if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + { + LOG_WARNING(callback_data->pMessage); + } + if (message_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + { + LOG_ERROR(callback_data->pMessage); + } + + return VK_FALSE; +} + +void VulkanAPI::createSurface(GLFWwindow * window) +{ + if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) + { + throw std::runtime_error("Failed to create window surface"); + } +} + +void VulkanAPI::pickPhysicalDevice() +{ + uint32_t device_count = 0; + vkEnumeratePhysicalDevices(instance, &device_count, nullptr); + + if (device_count == 0) + { + throw std::runtime_error("Failed to find GPUs with Vulkan support"); + } + + std::vector devices(device_count); + vkEnumeratePhysicalDevices(instance, &device_count, devices.data()); + + for (const auto & device : devices) + { + if (isDeviceSuitable(device)) + { + physical_device = device; + break; + } + } + + if (physical_device == VK_NULL_HANDLE) + { + throw std::runtime_error("Failed to find a suitable GPU"); + } + + queue_family_indices = findQueueFamilies(physical_device); + + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(physical_device, &device_properties); +} + +bool VulkanAPI::isDeviceSuitable(VkPhysicalDevice device) +{ + QueueFamilyIndices indices = findQueueFamilies(device); + + bool extensions_supported = checkDeviceExtensionSupport(device); + + bool swap_chain_adequate = false; + if (extensions_supported) + { + SwapChainSupportDetails swap_chain_support = querySwapChainSupport(device); + swap_chain_adequate = !swap_chain_support.formats.empty() && !swap_chain_support.present_modes.empty(); + } + + return indices.isComplete() && extensions_supported && swap_chain_adequate; +} + +QueueFamilyIndices VulkanAPI::findQueueFamilies(VkPhysicalDevice device) +{ + QueueFamilyIndices indices; + + uint32_t queue_family_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr); + std::vector queue_families(queue_family_count); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data()); + + int i = 0; + for (const auto & queue_family : queue_families) + { + if (queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) + { + indices.graphics_family = i; + } + + VkBool32 present_support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &present_support); + if (present_support) + { + indices.present_family = i; + } + + if (indices.isComplete()) + { + break; + } + + i++; + } + + return indices; +} + +bool VulkanAPI::checkDeviceExtensionSupport(VkPhysicalDevice device) +{ + uint32_t extension_count; + vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, nullptr); + std::vector available_extensions(extension_count); + vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, available_extensions.data()); + + std::set required_extensions(device_extensions.begin(), device_extensions.end()); + + for (const auto & extension : available_extensions) + { + required_extensions.erase(extension.extensionName); + } + + return required_extensions.empty(); +} + +SwapChainSupportDetails VulkanAPI::querySwapChainSupport(VkPhysicalDevice device) +{ + SwapChainSupportDetails details; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); + + uint32_t format_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &format_count, nullptr); + if (format_count != 0) + { + details.formats.resize(format_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &format_count, details.formats.data()); + } + + uint32_t present_mode_count; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_mode_count, nullptr); + if (present_mode_count != 0) + { + details.present_modes.resize(present_mode_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &present_mode_count, details.present_modes.data()); + } + + return details; +} + +void VulkanAPI::createLogicalDevice() +{ + std::vector queue_create_infos; + std::set unique_queue_families = { + queue_family_indices.graphics_family.value(), + queue_family_indices.present_family.value() + }; + + float queue_priority = 1.0f; + for (uint32_t queue_family : unique_queue_families) + { + VkDeviceQueueCreateInfo queue_create_info = {}; + queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info.queueFamilyIndex = queue_family; + queue_create_info.queueCount = 1; + queue_create_info.pQueuePriorities = &queue_priority; + queue_create_infos.push_back(queue_create_info); + } + + VkPhysicalDeviceFeatures device_features = {}; + + VkDeviceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_info.pQueueCreateInfos = queue_create_infos.data(); + create_info.queueCreateInfoCount = static_cast(queue_create_infos.size()); + create_info.pEnabledFeatures = &device_features; + create_info.enabledExtensionCount = static_cast(device_extensions.size()); + create_info.ppEnabledExtensionNames = device_extensions.data(); + + #ifndef NDEBUG + create_info.enabledLayerCount = static_cast(validation_layers.size()); + create_info.ppEnabledLayerNames = validation_layers.data(); + #else + create_info.enabledLayerCount = 0; + #endif + + VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamic_rendering_features = {}; + dynamic_rendering_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR; + dynamic_rendering_features.dynamicRendering = VK_TRUE; + + create_info.pNext = &dynamic_rendering_features; + + VK_CHECK( + vkCreateDevice(physical_device, &create_info, nullptr, &device), + "Failed to create logical device" + ); + + vkGetDeviceQueue(device, queue_family_indices.graphics_family.value(), 0, &graphics_queue); + vkGetDeviceQueue(device, queue_family_indices.present_family.value(), 0, &present_queue); +} + +void VulkanAPI::createSwapChain(GLFWwindow * window) +{ + SwapChainSupportDetails swap_chain_support = querySwapChainSupport(physical_device); + + VkSurfaceFormatKHR surface_format = chooseSwapSurfaceFormat(swap_chain_support.formats); + VkPresentModeKHR present_mode = chooseSwapPresentMode(swap_chain_support.present_modes); + VkExtent2D extent = chooseSwapExtent(swap_chain_support.capabilities, window); + + uint32_t image_count = swap_chain_support.capabilities.minImageCount + 1; + if (swap_chain_support.capabilities.maxImageCount > 0 && image_count > swap_chain_support.capabilities.maxImageCount) + { + image_count = swap_chain_support.capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + create_info.surface = surface; + create_info.minImageCount = image_count; + create_info.imageFormat = surface_format.format; + create_info.imageColorSpace = surface_format.colorSpace; + create_info.imageExtent = extent; + create_info.imageArrayLayers = 1; + create_info.imageUsage = VK_IMAGE_USAGE_TRANSFER_DST_BIT; + + uint32_t indices[] = { + queue_family_indices.graphics_family.value(), + queue_family_indices.present_family.value() + }; + + if (queue_family_indices.graphics_family != queue_family_indices.present_family) + { + create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + create_info.queueFamilyIndexCount = 2; + create_info.pQueueFamilyIndices = indices; + } + else + { + create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + create_info.queueFamilyIndexCount = 0; + create_info.pQueueFamilyIndices = nullptr; + } + + create_info.preTransform = swap_chain_support.capabilities.currentTransform; + create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + create_info.presentMode = present_mode; + create_info.clipped = VK_TRUE; + create_info.oldSwapchain = VK_NULL_HANDLE; + + VK_CHECK( + vkCreateSwapchainKHR(device, &create_info, nullptr, &swap_chain), + "Failed to create swap chain" + ); + + vkGetSwapchainImagesKHR(device, swap_chain, &image_count, nullptr); + swap_chain_images.resize(image_count); + vkGetSwapchainImagesKHR(device, swap_chain, &image_count, swap_chain_images.data()); + + swap_chain_image_format = surface_format.format; + swap_chain_extent = extent; +} + +void VulkanAPI::recreateSwapChain(GLFWwindow * window) +{ + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) + { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + + vkDeviceWaitIdle(device); + + vkDestroyImage(device, draw_image, nullptr); + vkUnmapMemory(device, draw_image_memory); + vkFreeMemory(device, draw_image_memory, nullptr); + + vkDestroyImageView(device, color_attachement_view, nullptr); + vkFreeMemory(device, color_attachement_memory, nullptr); + vkDestroyImage(device, color_attachement_image, nullptr); + + vkDestroyPipeline(device, graphics_pipeline, nullptr); + vkDestroyPipelineLayout(device, pipeline_layout, nullptr); + + vkDestroySwapchainKHR(device, swap_chain, nullptr); + + + createSwapChain(window); + createColorResources(); + createPipeline(); + createDrawImage(); +} + +VkSurfaceFormatKHR VulkanAPI::chooseSwapSurfaceFormat(const std::vector & available_formats) +{ + for (const auto & available_format : available_formats) + { + if (available_format.format == VK_FORMAT_B8G8R8A8_SRGB && available_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + { + return available_format; + } + } + + return available_formats[0]; +} + +VkPresentModeKHR VulkanAPI::chooseSwapPresentMode(const std::vector & available_present_modes) +{ + for (const auto & available_present_mode : available_present_modes) + { + if (available_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) + { + return available_present_mode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D VulkanAPI::chooseSwapExtent(const VkSurfaceCapabilitiesKHR & capabilities, GLFWwindow * window) +{ + if (capabilities.currentExtent.width != UINT32_MAX) + { + return capabilities.currentExtent; + } + else + { + int width, height; + glfwGetFramebufferSize(window, &width, &height); + + VkExtent2D actual_extent = { + static_cast(width), + static_cast(height) + }; + + actual_extent.width = std::clamp(actual_extent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actual_extent.height = std::clamp(actual_extent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + + return actual_extent; + } +} + +void VulkanAPI::createCommandPool() +{ + QueueFamilyIndices queue_family_indices = findQueueFamilies(physical_device); + + VkCommandPoolCreateInfo pool_info = {}; + pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + pool_info.queueFamilyIndex = queue_family_indices.graphics_family.value(); + pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + + VK_CHECK( + vkCreateCommandPool(device, &pool_info, nullptr, &command_pool), + "Failed to create command pool" + ); +} + +void VulkanAPI::createCommandBuffer() +{ + render_command_buffers.resize(max_frames_in_flight); + copy_command_buffers.resize(max_frames_in_flight); + + VkCommandBufferAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.commandPool = command_pool; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandBufferCount = static_cast(render_command_buffers.size()); + + VK_CHECK( + vkAllocateCommandBuffers(device, &alloc_info, render_command_buffers.data()), + "Failed to allocate command buffers" + ); + VK_CHECK( + vkAllocateCommandBuffers(device, &alloc_info, copy_command_buffers.data()), + "Failed to allocate command buffers" + ); +} + +void VulkanAPI::createSyncObjects() +{ + image_available_semaphores.resize(max_frames_in_flight); + render_finished_semaphores.resize(max_frames_in_flight); + swap_chain_updated_semaphores.resize(max_frames_in_flight); + in_flight_fences.resize(max_frames_in_flight); + + VkSemaphoreCreateInfo semaphore_info = {}; + semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; + + VkFenceCreateInfo fence_info = {}; + fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; + fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (size_t i = 0; i < static_cast(max_frames_in_flight); i++) + { + VK_CHECK( + vkCreateSemaphore(device, &semaphore_info, nullptr, &image_available_semaphores[i]), + "Failed to create semaphores" + ); + VK_CHECK( + vkCreateSemaphore(device, &semaphore_info, nullptr, &render_finished_semaphores[i]), + "Failed to create semaphores" + ); + VK_CHECK( + vkCreateSemaphore(device, &semaphore_info, nullptr, &swap_chain_updated_semaphores[i]), + "Failed to create semaphores" + ); + VK_CHECK( + vkCreateFence(device, &fence_info, nullptr, &in_flight_fences[i]), + "Failed to create fences" + ); + } +} + +void VulkanAPI::createColorResources() +{ + color_attachement_format = swap_chain_image_format; + color_attachement_extent = swap_chain_extent; + + createImage( + color_attachement_extent.width, + color_attachement_extent.height, + 1, + color_attachement_format, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + color_attachement_image, + color_attachement_memory + ); + createImageView( + color_attachement_image, + color_attachement_format, + VK_IMAGE_ASPECT_COLOR_BIT, + color_attachement_view + ); + + transitionImageLayout( + color_attachement_image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 0, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT + ); +} + +void VulkanAPI::createPipeline() +{ + auto vert_shader_code = readFile("shaders/simple_shader.vert.spv"); + auto frag_shader_code = readFile("shaders/simple_shader.frag.spv"); + + VkShaderModule vert_shader_module = createShaderModule(vert_shader_code); + VkShaderModule frag_shader_module = createShaderModule(frag_shader_code); + + VkPipelineShaderStageCreateInfo vert_shader_stage_info = {}; + vert_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vert_shader_stage_info.module = vert_shader_module; + vert_shader_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo frag_shader_stage_info = {}; + frag_shader_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + frag_shader_stage_info.module = frag_shader_module; + frag_shader_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo shader_stages[] = {vert_shader_stage_info, frag_shader_stage_info}; + + + VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; + vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vertex_input_info.vertexBindingDescriptionCount = 0; + vertex_input_info.pVertexBindingDescriptions = nullptr; + vertex_input_info.vertexAttributeDescriptionCount = 0; + vertex_input_info.pVertexAttributeDescriptions = nullptr; + + + VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; + input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; + input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; + input_assembly.primitiveRestartEnable = VK_FALSE; + + + VkViewport viewport = {}; + viewport.x = 0.0f; + viewport.y = 0.0f; + viewport.width = static_cast(swap_chain_extent.width); + viewport.height = static_cast(swap_chain_extent.height); + viewport.minDepth = 0.0f; + viewport.maxDepth = 1.0f; + + VkRect2D scissor = {}; + scissor.offset = {0, 0}; + scissor.extent = swap_chain_extent; + + VkPipelineViewportStateCreateInfo viewport_state = {}; + viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + viewport_state.viewportCount = 1; + viewport_state.pViewports = &viewport; + viewport_state.scissorCount = 1; + viewport_state.pScissors = &scissor; + + + VkPipelineRasterizationStateCreateInfo rasterizer = {}; + rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = VK_POLYGON_MODE_FILL; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; + rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + + + VkPipelineMultisampleStateCreateInfo multisampling = {}; + multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; + + + VkPipelineColorBlendAttachmentState color_blend_attachment = {}; + color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + color_blend_attachment.blendEnable = VK_FALSE; + + + VkPipelineColorBlendStateCreateInfo color_blending = {}; + color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; + color_blending.logicOpEnable = VK_FALSE; + color_blending.attachmentCount = 1; + color_blending.pAttachments = &color_blend_attachment; + + + std::vector formats = {color_attachement_format}; + + VkPipelineRenderingCreateInfo rendering_info = {}; + rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO; + rendering_info.colorAttachmentCount = static_cast(formats.size()); + rendering_info.pColorAttachmentFormats = formats.data(); + // rendering_info.depthAttachmentFormat = VK_FORMAT_D32_SFLOAT; + + + VkPipelineLayoutCreateInfo pipeline_layout_info = {}; + pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pipeline_layout_info.setLayoutCount = 0; + pipeline_layout_info.pSetLayouts = nullptr; + pipeline_layout_info.pushConstantRangeCount = 0; + pipeline_layout_info.pPushConstantRanges = nullptr; + + VK_CHECK( + vkCreatePipelineLayout(device, &pipeline_layout_info, nullptr, &pipeline_layout), + "Failed to create pipeline layout" + ); + + + VkGraphicsPipelineCreateInfo pipeline_info = {}; + pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + pipeline_info.stageCount = 2; + pipeline_info.pStages = shader_stages; + pipeline_info.pVertexInputState = &vertex_input_info; + pipeline_info.pInputAssemblyState = &input_assembly; + pipeline_info.pViewportState = &viewport_state; + pipeline_info.pRasterizationState = &rasterizer; + pipeline_info.pMultisampleState = &multisampling; + pipeline_info.pColorBlendState = &color_blending; + pipeline_info.layout = pipeline_layout; + pipeline_info.pNext = &rendering_info; + + VK_CHECK( + vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &graphics_pipeline), + "Failed to create graphics pipeline" + ); + + vkDestroyShaderModule(device, frag_shader_module, nullptr); + vkDestroyShaderModule(device, vert_shader_module, nullptr); +} + +std::vector VulkanAPI::readFile(const std::string & filename) +{ + std::ifstream file + { + filename, + std::ios::ate | std::ios::binary + }; + + if (!file.is_open()) + { + throw std::runtime_error("Failed to open file: " + filename); + } + + size_t file_size = static_cast(file.tellg()); + std::vector buffer(file_size); + + file.seekg(0); + file.read(buffer.data(), file_size); + file.close(); + + return buffer; +} + +VkShaderModule VulkanAPI::createShaderModule(const std::vector & code) +{ + VkShaderModuleCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + create_info.codeSize = code.size(); + create_info.pCode = reinterpret_cast(code.data()); + + VkShaderModule shader_module; + VK_CHECK( + vkCreateShaderModule(device, &create_info, nullptr, &shader_module), + "Failed to create shader module" + ); + + return shader_module; +} + +void VulkanAPI::createDrawImage() +{ + draw_image_format = VK_FORMAT_R8G8B8A8_SRGB; + draw_image_extent = swap_chain_extent; + + createImage( + draw_image_extent.width, + draw_image_extent.height, + 1, + draw_image_format, + VK_IMAGE_TILING_LINEAR, + VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + draw_image, + draw_image_memory + ); + + transitionImageLayout( + draw_image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_ASPECT_COLOR_BIT, + 1, + 0, + 0, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT + ); + + // map memory + vkMapMemory(device, draw_image_memory, 0, VK_WHOLE_SIZE, 0, &draw_image_mapped_memory); +} + +void VulkanAPI::clearPixels() +{ + std::memset(draw_image_mapped_memory, 0, draw_image_extent.width * draw_image_extent.height * 4); +} + +void VulkanAPI::putPixel(uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + if (x >= draw_image_extent.width || y >= draw_image_extent.height) + { + throw std::runtime_error("Pixel coordinates out of range"); + } + + uint8_t * pixel = reinterpret_cast(draw_image_mapped_memory); + pixel += (y * draw_image_extent.width + x) * 4; + pixel[0] = r; + pixel[1] = g; + pixel[2] = b; + pixel[3] = a; + +} + + +uint32_t VulkanAPI::findMemoryType( + uint32_t type_filter, + VkMemoryPropertyFlags properties +) +{ + VkPhysicalDeviceMemoryProperties mem_properties; + vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_properties); + + for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) + { + if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) + { + return i; + } + } + + throw std::runtime_error("Failed to find suitable memory type"); +} + +void VulkanAPI::createImage( + uint32_t width, + uint32_t height, + uint32_t mip_levels, + VkFormat format, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkMemoryPropertyFlags properties, + VkImage & image, + VkDeviceMemory & image_memory +) +{ + VkImageCreateInfo image_info = {}; + image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_info.imageType = VK_IMAGE_TYPE_2D; + image_info.extent.width = width; + image_info.extent.height = height; + image_info.extent.depth = 1; + image_info.mipLevels = mip_levels; + image_info.arrayLayers = 1; + image_info.format = format; + image_info.tiling = tiling; + image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_info.usage = usage; + image_info.samples = VK_SAMPLE_COUNT_1_BIT; + image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + + VK_CHECK( + vkCreateImage(device, &image_info, nullptr, &image), + "Failed to create image" + ); + + VkMemoryRequirements mem_requirements; + vkGetImageMemoryRequirements(device, image, &mem_requirements); + + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.allocationSize = mem_requirements.size; + alloc_info.memoryTypeIndex = findMemoryType(mem_requirements.memoryTypeBits, properties); + + VK_CHECK( + vkAllocateMemory(device, &alloc_info, nullptr, &image_memory), + "Failed to allocate image memory" + ); + + vkBindImageMemory(device, image, image_memory, 0); +} + +void VulkanAPI::createImageView( + VkImage image, + VkFormat format, + VkImageAspectFlags aspect_flags, + VkImageView & image_view +) +{ + VkImageViewCreateInfo view_info = {}; + view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + view_info.image = image; + view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; + view_info.format = format; + view_info.subresourceRange.aspectMask = aspect_flags; + view_info.subresourceRange.baseMipLevel = 0; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.baseArrayLayer = 0; + view_info.subresourceRange.layerCount = 1; + + VK_CHECK( + vkCreateImageView(device, &view_info, nullptr, &image_view), + "Failed to create image view" + ); +} + +void VulkanAPI::transitionImageLayout( + VkImage image, + VkImageLayout old_layout, + VkImageLayout new_layout, + VkImageAspectFlags aspect_mask, + uint32_t mip_levels, + VkAccessFlags src_access_mask, + VkAccessFlags dst_access_mask, + VkPipelineStageFlags src_stage_mask, + VkPipelineStageFlags dst_stage_mask +) +{ + VkCommandBufferAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + alloc_info.commandPool = command_pool; + alloc_info.commandBufferCount = 1; + + VkCommandBuffer command_buffer; + VK_CHECK( + vkAllocateCommandBuffers(device, &alloc_info, &command_buffer), + "Failed to allocate command buffers" + ); + + VkCommandBufferBeginInfo begin_info = {}; + begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + + VK_CHECK( + vkBeginCommandBuffer(command_buffer, &begin_info), + "Failed to begin recording command buffer" + ); + + VkImageMemoryBarrier barrier = {}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = aspect_mask; + barrier.subresourceRange.baseMipLevel = 0; + barrier.subresourceRange.levelCount = mip_levels; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.srcAccessMask = src_access_mask; + barrier.dstAccessMask = dst_access_mask; + + vkCmdPipelineBarrier( + command_buffer, + src_stage_mask, + dst_stage_mask, + 0, + 0, + nullptr, + 0, + nullptr, + 1, + &barrier + ); + + VK_CHECK( + vkEndCommandBuffer(command_buffer), + "Failed to end recording command buffer" + ); + + VkSubmitInfo submit_info = {}; + submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &command_buffer; + + VK_CHECK( + vkQueueSubmit(graphics_queue, 1, &submit_info, VK_NULL_HANDLE), + "Failed to submit queue" + ); + + VK_CHECK( + vkQueueWaitIdle(graphics_queue), + "Failed to wait for queue" + ); + + vkFreeCommandBuffers(device, command_pool, 1, &command_buffer); +} + diff --git a/src/app/vulkan/VulkanAPI.hpp b/src/app/vulkan/VulkanAPI.hpp new file mode 100644 index 00000000..23da177a --- /dev/null +++ b/src/app/vulkan/VulkanAPI.hpp @@ -0,0 +1,210 @@ +#pragma once + +#define GLFW_INCLUDE_VULKAN +#include + +// #include +#include + +#include +#include +#include + +// #define NDEBUG + +#define VK_CHECK(function, message) \ + { \ + VkResult result = function; \ + if (result != VK_SUCCESS) \ + { \ + throw std::runtime_error(std::string(message) + " (" + std::string(string_VkResult(result)) + ")"); \ + } \ + } + + +struct QueueFamilyIndices +{ + std::optional graphics_family; + std::optional present_family; + + bool isComplete() + { + return graphics_family.has_value() && present_family.has_value(); + } +}; + +struct SwapChainSupportDetails +{ + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector present_modes; +}; + + +class VulkanAPI +{ + +public: + + VulkanAPI(GLFWwindow * window); + ~VulkanAPI(); + + VulkanAPI(const VulkanAPI &) = delete; + VulkanAPI(VulkanAPI &&) = delete; + VulkanAPI & operator=(const VulkanAPI &) = delete; + VulkanAPI & operator=(VulkanAPI &&) = delete; + + void transitionImageLayout( + VkImage image, + VkImageLayout oldLayout, + VkImageLayout newLayout, + VkImageAspectFlags aspectMask, + uint32_t mipLevels, + VkAccessFlags srcAccessMask, + VkAccessFlags dstAccessMask, + VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask + ); + + void recreateSwapChain(GLFWwindow * window); + + uint32_t width() const { return draw_image_extent.width; } + uint32_t height() const { return draw_image_extent.height; } + void clearPixels(); + void putPixel(uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255); + + + GLFWwindow * window; + + VkInstance instance; + VkDebugUtilsMessengerEXT debug_messenger; + VkPhysicalDevice physical_device = VK_NULL_HANDLE; + + VkSurfaceKHR surface; + + VkDevice device; + VkQueue graphics_queue; + VkQueue present_queue; + QueueFamilyIndices queue_family_indices; + + VkSwapchainKHR swap_chain; + std::vector swap_chain_images; + VkFormat swap_chain_image_format; + VkExtent2D swap_chain_extent; + + VkPipelineLayout pipeline_layout; + VkPipeline graphics_pipeline; + + VkCommandPool command_pool; + std::vector render_command_buffers; + std::vector copy_command_buffers; + + std::vector image_available_semaphores; + std::vector render_finished_semaphores; + std::vector swap_chain_updated_semaphores; + std::vector in_flight_fences; + + VkImage color_attachement_image; + VkDeviceMemory color_attachement_memory; + VkImageView color_attachement_view; + VkFormat color_attachement_format; + VkExtent2D color_attachement_extent; + + VkImage draw_image; + VkDeviceMemory draw_image_memory; + void * draw_image_mapped_memory; + VkFormat draw_image_format; + VkExtent2D draw_image_extent; + + const int max_frames_in_flight = 2; + int current_frame = 0; + +private: + + const std::vector validation_layers = { + "VK_LAYER_KHRONOS_validation" + }; + + std::vector device_extensions = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME + }; + + + + void createInstance(); + bool checkValidationLayerSupport(); + std::vector getRequiredExtensions(); + + void createSurface(GLFWwindow * window); + + void setupDebugMessenger(); + void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT & create_info); + VkResult CreateDebugUtilsMessengerEXT( + VkInstance instance, + const VkDebugUtilsMessengerCreateInfoEXT * create_info, + const VkAllocationCallbacks * allocator, + VkDebugUtilsMessengerEXT * debug_messenger + ); + void DestroyDebugUtilsMessengerEXT( + VkInstance instance, + VkDebugUtilsMessengerEXT debug_messenger, + const VkAllocationCallbacks * allocator + ); + static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, + VkDebugUtilsMessageTypeFlagsEXT message_type, + const VkDebugUtilsMessengerCallbackDataEXT * callback_data, + void * user_data + ); + + void pickPhysicalDevice(); + bool isDeviceSuitable(VkPhysicalDevice device); + QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device); + bool checkDeviceExtensionSupport(VkPhysicalDevice device); + SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device); + + void createLogicalDevice(); + + void createSwapChain(GLFWwindow * window); + VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector & available_formats); + VkPresentModeKHR chooseSwapPresentMode(const std::vector & available_present_modes); + VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR & capabilities, GLFWwindow * window); + + void createCommandPool(); + void createCommandBuffer(); + + void createSyncObjects(); + + void createColorResources(); + + void createPipeline(); + static std::vector readFile(const std::string & filename); + VkShaderModule createShaderModule(const std::vector & code); + + void createDrawImage(); + + + uint32_t findMemoryType( + uint32_t type_filter, + VkMemoryPropertyFlags properties + ); + void createImage( + uint32_t width, + uint32_t height, + uint32_t mip_levels, + VkFormat format, + VkImageTiling tiling, + VkImageUsageFlags usage, + VkMemoryPropertyFlags properties, + VkImage & image, + VkDeviceMemory & image_memory + ); + void createImageView( + VkImage image, + VkFormat format, + VkImageAspectFlags aspect_flags, + VkImageView & image_view + ); + +}; \ No newline at end of file diff --git a/src/app/window/input.hpp b/src/app/window/input.hpp index edd58162..981ec618 100644 --- a/src/app/window/input.hpp +++ b/src/app/window/input.hpp @@ -1,6 +1,6 @@ #pragma once -#include "defines.hpp" +#include "define.hpp" #include diff --git a/src/app/window/window.hpp b/src/app/window/window.hpp index f7feedb8..903a1d89 100644 --- a/src/app/window/window.hpp +++ b/src/app/window/window.hpp @@ -1,6 +1,6 @@ #pragma once -#include "defines.hpp" +#include "define.hpp" #include "input.hpp" #include