diff options
author | Chuyan Zhang <me@zcy.moe> | 2024-09-09 00:30:29 -0700 |
---|---|---|
committer | Chuyan Zhang <me@zcy.moe> | 2024-09-09 00:30:29 -0700 |
commit | 7f14138e1baa2c40fb30d90ebcd45ad17b12e0a3 (patch) | |
tree | d9ad5dbb2871e3999480e55ffa24bea6e50f8dd4 | |
parent | 2ead02037dc89e987fbc0a021fe470e29d226cfd (diff) | |
download | iris-7f14138e1baa2c40fb30d90ebcd45ad17b12e0a3.tar.gz iris-7f14138e1baa2c40fb30d90ebcd45ad17b12e0a3.zip |
Fixing swapchain
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | CMakeLists.txt | 8 | ||||
-rw-r--r-- | assets/Roboto-Regular.ttf | bin | 0 -> 168260 bytes | |||
-rw-r--r-- | assets/debug.png | bin | 0 -> 3062239 bytes | |||
m--------- | ext/stb | 0 | ||||
-rw-r--r-- | src/app.cpp | 84 | ||||
-rw-r--r-- | src/vulkan_helper.cpp | 323 | ||||
-rw-r--r-- | src/vulkan_helper.h | 76 | ||||
-rw-r--r-- | src/vulkan_swapchain.cpp | 253 | ||||
-rw-r--r-- | src/vulkan_swapchain.h | 11 |
10 files changed, 688 insertions, 70 deletions
diff --git a/.gitmodules b/.gitmodules index 6ea0b14..0d0d572 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "ext/VulkanMemoryAllocator"] path = ext/VulkanMemoryAllocator url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator +[submodule "ext/stb"] + path = ext/stb + url = https://github.com/nothings/stb diff --git a/CMakeLists.txt b/CMakeLists.txt index 41401be..10fb56f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_CXX_STANDARD 20) set(PROJECT_ROOT ${CMAKE_SOURCE_DIR}) set(SRC_DIR ${PROJECT_ROOT}/src) set(EXT_DIR ${PROJECT_ROOT}/ext) +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DUSE_VULKAN_VALIDATION_LAYERS") # Add external libraries include(${EXT_DIR}/imgui.cmake) @@ -21,6 +22,9 @@ target_include_directories(tinygltf INTERFACE ${EXT_DIR}/tinygltf) add_library(tinyobjloader INTERFACE) target_include_directories(tinyobjloader INTERFACE ${EXT_DIR}/tinyobjloader) +add_library(stb INTERFACE) +target_include_directories(stb INTERFACE ${EXT_DIR}/stb) + file(GLOB_RECURSE SOURCES "${SRC_DIR}/*.cpp") # Add executable from your source files @@ -32,12 +36,12 @@ target_link_libraries(IrisRenderer PRIVATE glfw) find_package(Vulkan REQUIRED) target_link_libraries(IrisRenderer PRIVATE Vulkan::Vulkan) -find_package(VulkanMemoryAllocator REQUIRED) +find_package(VulkanMemoryAllocator CONFIG REQUIRED) target_link_libraries(IrisRenderer PRIVATE GPUOpen::VulkanMemoryAllocator) # Link external libraries to your project target_link_libraries(IrisRenderer PRIVATE - argparse imgui tinygltf tinyobjloader + argparse imgui tinygltf tinyobjloader stb dl pthread X11 Xxf86vm Xrandr Xi) # Optional: Include additional compiler options or flags diff --git a/assets/Roboto-Regular.ttf b/assets/Roboto-Regular.ttf Binary files differnew file mode 100644 index 0000000..2d116d9 --- /dev/null +++ b/assets/Roboto-Regular.ttf diff --git a/assets/debug.png b/assets/debug.png Binary files differnew file mode 100644 index 0000000..dca7c3a --- /dev/null +++ b/assets/debug.png diff --git a/ext/stb b/ext/stb new file mode 160000 +Subproject f75e8d1cad7d90d72ef7a4661f1b994ef78b4e3 diff --git a/src/app.cpp b/src/app.cpp index a5b3247..6148e04 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1,37 +1,20 @@ +#include "imgui.h" #include "vulkan_swapchain.h" #include "imgui_impl_vulkan.h" #include "argparse/argparse.hpp" #include <cstdint> #include <sys/types.h> +#include <vk_mem_alloc.h> +#include <vulkan/vulkan_core.h> #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #include <cstdlib> #include <iostream> -#include <memory> #include <vector> -std::unique_ptr<iris::Swapchain> start_up(int width, int height) { - auto glfw_extensions = get_glfw_instance_extensions(); - iris::Device device({}, glfw_extensions); - - auto window = glfwCreateWindow(width, height, "IrisRenderer", nullptr, nullptr); - if (window == nullptr) { - std::cerr << "Failed to create GLFW window" << std::endl; - abort(); - } - return std::make_unique<iris::Swapchain>(window, device); -} - -void shut_down(std::unique_ptr<iris::Swapchain>& swapchain) { - ImGui_ImplVulkan_Shutdown(); - - glfwDestroyWindow(swapchain->window); - glfwTerminate(); -} - int main(int argc, char** argv) { argparse::ArgumentParser program("IrisRenderer"); program.add_argument("width") @@ -64,28 +47,69 @@ int main(int argc, char** argv) { glfwTerminate(); return -1; } + auto glfw_extensions = get_glfw_instance_extensions(); + for (const auto& extension : glfw_extensions) { + std::cerr << "GLFW extension: " << extension << std::endl; + } + + std::vector<std::string> layers; + iris::Device device({}, glfw_extensions); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + auto window = glfwCreateWindow(window_width, window_height, "IrisRenderer", nullptr, nullptr); + if (window == nullptr) { + std::cerr << "Failed to create GLFW window" << std::endl; + abort(); + } - auto swapchain = start_up(window_width, window_height); - while (!glfwWindowShouldClose(swapchain->window)) { + // load debug image + iris::Texture2D debug_texture = device.create_texture_from_image( + "assets/debug.png", + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VmaAllocationCreateInfo { + .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, + .usage = VMA_MEMORY_USAGE_AUTO, + }); + // end load debug image + + auto swapchain = iris::Swapchain(window, device, window_width, window_height); + std::cerr << "Swapchain created" << std::endl; + while (!glfwWindowShouldClose(swapchain.window)) { glfwPollEvents(); int display_w, display_h; - glfwGetFramebufferSize(swapchain->window, &display_w, &display_h); + glfwGetFramebufferSize(swapchain.window, &display_w, &display_h); if (display_w == 0 || display_h == 0) { break; } - swapchain->needs_recreate |= - (uint32_t) display_w != swapchain->width || - (uint32_t) display_h != swapchain->height; - if (swapchain->needs_recreate) { - swapchain->resize(display_w, display_h); - swapchain->needs_recreate = false; + swapchain.needs_recreate |= + (uint32_t) display_w != swapchain.width || + (uint32_t) display_h != swapchain.height; + if (swapchain.needs_recreate) { + swapchain.resize(display_w, display_h); + swapchain.needs_recreate = false; } + std::cerr << "Rendering frame" << std::endl; ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + swapchain.start_frame(); + std::cerr << "Frame started" << std::endl; + ImGui::NewFrame(); + ImGui::ShowDemoWindow(); + ImGui::Render(); + std::cerr << "ImGui rendered" << std::endl; + swapchain.display(debug_texture); + std::cerr << "Frame displayed" << std::endl; } - shut_down(swapchain); + ImGui_ImplVulkan_Shutdown(); + + glfwDestroyWindow(swapchain.window); + glfwTerminate(); + device.destroy(); + return 0; }
\ No newline at end of file diff --git a/src/vulkan_helper.cpp b/src/vulkan_helper.cpp index e533d2d..339b9c0 100644 --- a/src/vulkan_helper.cpp +++ b/src/vulkan_helper.cpp @@ -1,11 +1,25 @@ #include "vulkan_helper.h" #include "vulkan/vulkan_core.h" +#include <limits> +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" #include <cstdint> +#include <cstring> #include <cstdlib> #include <iostream> #include <memory> +#define VMA_IMPLEMENTATION #include <vk_mem_alloc.h> +#ifdef USE_VULKAN_VALIDATION_LAYERS +static VKAPI_ATTR VkBool32 VKAPI_CALL debug_report(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData) +{ + (void)flags; (void)object; (void)location; (void)messageCode; (void)pUserData; (void)pLayerPrefix; // Unused arguments + fprintf(stderr, "[vulkan] Debug report from ObjectType: %i\nMessage: %s\n\n", objectType, pMessage); + return VK_FALSE; +} +#endif + namespace iris { Device::Device(std::vector<std::string> layers, std::vector<std::string> instance_extensions) { @@ -44,6 +58,19 @@ Device::Device(std::vector<std::string> layers, std::vector<std::string> instanc VK_NULL_HANDLE, &instance)); +#ifdef USE_VULKAN_VALIDATION_LAYERS + layers.push_back("VK_LAYER_KHRONOS_validation"); + auto vkCreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); + if (vkCreateDebugReportCallback) { + VkDebugReportCallbackCreateInfoEXT debug_report_create_info = { + .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT, + .flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT, + .pfnCallback = debug_report, + }; + CHECK_VULKAN(vkCreateDebugReportCallback(instance, &debug_report_create_info, nullptr, &debugReportCallback)); + } +#endif + // Enumerate and select the physical device uint32_t physical_device_count = 0; CHECK_VULKAN(vkEnumeratePhysicalDevices( @@ -117,6 +144,7 @@ Device::Device(std::vector<std::string> layers, std::vector<std::string> instanc }; constexpr char *device_extensions[] = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME, VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, @@ -154,28 +182,121 @@ Device::Device(std::vector<std::string> layers, std::vector<std::string> instanc &allocator)); } -Device::~Device() { +// not handled by RAII, manually call at the end. +void Device::destroy() { vmaDestroyAllocator(allocator); vkDestroyDevice(device, VK_NULL_HANDLE); vkDestroyInstance(instance, VK_NULL_HANDLE); } +CommandBuffer::CommandBuffer(VkDevice device, + uint32_t queue_family_index, + VkQueue queue) + : device(device), queue(queue) +{ + VkCommandPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .queueFamilyIndex = queue_family_index, // TODO: query capabilities to find a proper queue index + }; + CHECK_VULKAN(vkCreateCommandPool( + device, + &pool_info, + VK_NULL_HANDLE, + &this->pool)); + + VkCommandBufferAllocateInfo buffer_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = this->pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = 1, + }; + CHECK_VULKAN(vkAllocateCommandBuffers( + device, + &buffer_info, + &buffer)); + + VkFenceCreateInfo fence_info = { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + }; + CHECK_VULKAN(vkCreateFence( + device, + &fence_info, + VK_NULL_HANDLE, + &fence)); +} + +CommandBuffer::~CommandBuffer() { + vkDestroyFence(device, fence, VK_NULL_HANDLE); + vkFreeCommandBuffers(device, pool, 1, &buffer); + vkDestroyCommandPool(device, pool, VK_NULL_HANDLE); +} + +CommandBuffer Device::create_command_buffer() { + return CommandBuffer(device, main_queue_family_index, graphics_queue); +} + +void CommandBuffer::begin(VkCommandBufferUsageFlags flags) { + CHECK_VULKAN(vkResetFences(device, 1, &fence)); + CHECK_VULKAN(vkResetCommandPool(device, pool, 0u)); + + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = flags, + }; + CHECK_VULKAN(vkBeginCommandBuffer(buffer, &begin_info)); +} + +void CommandBuffer::submit_sync() { + CHECK_VULKAN(vkEndCommandBuffer(buffer)); + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 0, + .pWaitSemaphores = VK_NULL_HANDLE, + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1, + .pCommandBuffers = &buffer, + }; + CHECK_VULKAN(vkQueueSubmit( + queue, + 1, + &submit_info, + fence)); + CHECK_VULKAN(vkWaitForFences( + device, + 1, + &fence, + VK_TRUE, + std::numeric_limits<uint64_t>::max())); +} + +void *Buffer_t::map() { + if (mapped_data == nullptr) { + CHECK_VULKAN(vmaMapMemory(allocator, allocation, &mapped_data)); + } + return mapped_data; +} + +void Buffer_t::unmap() { + if (mapped_data != nullptr) { + vmaUnmapMemory(allocator, allocation); + mapped_data = nullptr; + } +} Buffer_t::~Buffer_t() { vmaDestroyBuffer(allocator, buffer, allocation); } -Buffer Device::create_buffer(VkDeviceSize size, - VkBufferUsageFlags usage, - VmaMemoryUsage memory_usage) { +Buffer_t Device::create_buffer_raw(VkDeviceSize size, + VkBufferUsageFlags usage, + VmaAllocationCreateInfo create_info) +{ VkBufferCreateInfo buffer_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = usage, }; - VmaAllocationCreateInfo allocation_info = { - .usage = memory_usage, - }; Buffer_t buffer = { .allocator = this->allocator, .flags = usage, @@ -184,11 +305,18 @@ Buffer Device::create_buffer(VkDeviceSize size, CHECK_VULKAN(vmaCreateBuffer( allocator, &buffer_info, - &allocation_info, + &create_info, &buffer.buffer, &buffer.allocation, VK_NULL_HANDLE)); - return std::make_shared<Buffer_t>(buffer); + return buffer; +} + +Buffer Device::create_buffer(VkDeviceSize size, + VkBufferUsageFlags usage, + VmaAllocationCreateInfo create_info) +{ + return std::make_shared<Buffer_t>(create_buffer_raw(size, usage, create_info)); } Texture2D_t::~Texture2D_t() { @@ -196,10 +324,11 @@ Texture2D_t::~Texture2D_t() { // TODO: optionally destroy image view, if created } -Texture2D Device::create_texture(VkExtent2D extent, - VkFormat format, - VkImageUsageFlags usage, - VmaMemoryUsage memory_usage) { +Texture2D_t Device::create_texture_raw(VkExtent2D extent, + VkFormat format, + VkImageUsageFlags usage, + VmaAllocationCreateInfo create_info) +{ VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, @@ -211,21 +340,181 @@ Texture2D Device::create_texture(VkExtent2D extent, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = usage, }; - VmaAllocationCreateInfo allocation_info = { - .usage = memory_usage, - }; Texture2D_t texture = { .allocator = this->allocator, + .layout = VK_IMAGE_LAYOUT_UNDEFINED, .flags = usage, .extent = extent, }; CHECK_VULKAN(vmaCreateImage( allocator, &image_info, - &allocation_info, + &create_info, &texture.image, &texture.allocation, VK_NULL_HANDLE)); + return texture; +} + +Texture2D Device::create_texture(VkExtent2D extent, + VkFormat format, + VkImageUsageFlags usage, + VmaAllocationCreateInfo create_info) +{ + return std::make_shared<Texture2D_t>(create_texture_raw(extent, format, usage, create_info)); +} + +Texture2D Device::create_texture_from_image(const char* filename, + VkFormat format, + VkImageUsageFlags usage, + VmaAllocationCreateInfo create_info) { + int width, height, channels; + stbi_uc* pixels = stbi_load(filename, &width, &height, &channels, STBI_rgb_alpha); + if (pixels == nullptr) { + // TODO throw an exception + std::cerr << "Failed to load image: " << filename << std::endl; + abort(); + } + + // destroy after use, don't need to wrap with shared_ptr + auto staging_buf = create_buffer_raw( + width * height * 4, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VmaAllocationCreateInfo { + .flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT + | VMA_ALLOCATION_CREATE_MAPPED_BIT, + .usage = VMA_MEMORY_USAGE_AUTO, + }); + std::memcpy( + staging_buf.map(), + pixels, + width * height * 4); + staging_buf.unmap(); + stbi_image_free(pixels); + + VkExtent2D extent = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)}; + Texture2D_t texture = create_texture_raw(extent, format, usage, create_info); + + // Transit image layout for copying + { + CommandBuffer cmd_buf = create_command_buffer(); + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info)); + VkImageMemoryBarrier barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = texture.image, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + vkCmdPipelineBarrier( + cmd_buf.buffer, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer)); + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &cmd_buf.buffer, + }; + CHECK_VULKAN(vkQueueSubmit(graphics_queue, 1, &submit_info, cmd_buf.fence)); + CHECK_VULKAN(vkWaitForFences(cmd_buf.device, 1, &cmd_buf.fence, VK_TRUE, std::numeric_limits<uint64_t>::max())); + } + + // copy staging buffer to texture + { + CommandBuffer cmd_buf = create_command_buffer(); + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info)); + VkBufferImageCopy region = { + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {(uint32_t) width, (uint32_t) height, 1}, + }; + vkCmdCopyBufferToImage( + cmd_buf.buffer, + staging_buf.buffer, + texture.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + ®ion); + CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer)); + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &cmd_buf.buffer, + }; + CHECK_VULKAN(vkQueueSubmit(graphics_queue, 1, &submit_info, cmd_buf.fence)); + CHECK_VULKAN(vkWaitForFences(cmd_buf.device, 1, &cmd_buf.fence, VK_TRUE, std::numeric_limits<uint64_t>::max())); + } + + // Transit image layout back for use + { + CommandBuffer cmd_buf = create_command_buffer(); + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info)); + VkImageMemoryBarrier barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = texture.image, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + vkCmdPipelineBarrier( + cmd_buf.buffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &barrier); + CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer)); + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &cmd_buf.buffer, + }; + CHECK_VULKAN(vkQueueSubmit(graphics_queue, 1, &submit_info, cmd_buf.fence)); + CHECK_VULKAN(vkWaitForFences(cmd_buf.device, 1, &cmd_buf.fence, VK_TRUE, std::numeric_limits<uint64_t>::max())); + } + texture.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; return std::make_shared<Texture2D_t>(texture); } diff --git a/src/vulkan_helper.h b/src/vulkan_helper.h index b6f270a..ebdd691 100644 --- a/src/vulkan_helper.h +++ b/src/vulkan_helper.h @@ -1,19 +1,20 @@ #include <memory> #include <vulkan/vulkan_core.h> +#include <vulkan/vk_enum_string_helper.h> #include <vk_mem_alloc.h> #include <cstdint> #include <vector> #include <string> -#define CHECK_VULKAN(result) \ - do { \ - VkResult res = result; \ - if (res != VK_SUCCESS) { \ - /* TODO: throw error instead of returning */ \ - std::cerr << "Vulkan error: " << res << std::endl; \ - abort(); \ - } \ +#define CHECK_VULKAN(result) \ + do { \ + VkResult res = result; \ + if (res != VK_SUCCESS) { \ + /* TODO: throw error instead of returning */ \ + std::cerr << "Vulkan error: " << string_VkResult(res) << std::endl; \ + abort(); \ + } \ } while (0) namespace iris { @@ -25,8 +26,11 @@ struct Buffer_t { VkBufferUsageFlags flags; VkDeviceSize size; + void *mapped_data = nullptr; ~Buffer_t(); + void* map(); + void unmap(); }; typedef std::shared_ptr<Buffer_t> Buffer; @@ -36,6 +40,7 @@ struct Texture2D_t { VmaAllocator allocator; VmaAllocation allocation; VkImageView image_view; + VkImageLayout layout; VkImageUsageFlags flags; VkExtent2D extent; @@ -45,6 +50,22 @@ struct Texture2D_t { typedef std::shared_ptr<Texture2D_t> Texture2D; +// This is a really brute-force implementation, +// every command pool contains only 1 command buffer +struct CommandBuffer { + VkDevice device; + VkCommandPool pool; + VkCommandBuffer buffer; + VkFence fence; + VkQueue queue; + + CommandBuffer(VkDevice device, uint32_t queue_family_index, VkQueue queue); + ~CommandBuffer(); + + void begin(VkCommandBufferUsageFlags flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + void submit_sync(); +}; + struct Device { VkInstance instance; VkPhysicalDevice physical_device; @@ -52,22 +73,55 @@ struct Device { uint32_t main_queue_family_index; VkQueue graphics_queue; VmaAllocator allocator; +#ifdef USE_VULKAN_VALIDATION_LAYERS + VkDebugReportCallbackEXT debugReportCallback; +#endif + Device() = delete; Device( std::vector<std::string> layers, std::vector<std::string> instance_extensions); - ~Device(); + void destroy(); + + Buffer_t create_buffer_raw( + VkDeviceSize size, + VkBufferUsageFlags usage, + VmaAllocationCreateInfo create_info = { + .usage = VMA_MEMORY_USAGE_AUTO, + }); + + Texture2D_t create_texture_raw( + VkExtent2D extent, + VkFormat format, + VkImageUsageFlags usage, + VmaAllocationCreateInfo create_info = { + .usage = VMA_MEMORY_USAGE_AUTO, + }); Buffer create_buffer( VkDeviceSize size, VkBufferUsageFlags usage, - VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_AUTO); + VmaAllocationCreateInfo create_info = { + .usage = VMA_MEMORY_USAGE_AUTO, + }); Texture2D create_texture( VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, - VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_AUTO); + VmaAllocationCreateInfo create_info = { + .usage = VMA_MEMORY_USAGE_AUTO, + }); + + Texture2D create_texture_from_image( + const char* filename, + VkFormat format, + VkImageUsageFlags usage, + VmaAllocationCreateInfo create_info = { + .usage = VMA_MEMORY_USAGE_AUTO, + }); + + CommandBuffer create_command_buffer(); }; } // namespace iris
\ No newline at end of file diff --git a/src/vulkan_swapchain.cpp b/src/vulkan_swapchain.cpp index fde4388..41d27a5 100644 --- a/src/vulkan_swapchain.cpp +++ b/src/vulkan_swapchain.cpp @@ -1,6 +1,7 @@ #include "vulkan_swapchain.h" #include "GLFW/glfw3.h" +#include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_vulkan.h" #include "vulkan/vulkan_core.h" @@ -42,13 +43,18 @@ void Swapchain::resize(uint32_t new_width, uint32_t new_height) { vkDestroySwapchainKHR(device.device, swapchain, nullptr); } + VkSurfaceCapabilitiesKHR surface_capabilities; + CHECK_VULKAN(vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + device.physical_device, + surface, + &surface_capabilities)); VkSwapchainCreateInfoKHR swapchain_create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface, .minImageCount = SWAPCHAIN_IMAGE_COUNT, .imageFormat = VK_FORMAT_B8G8R8A8_UNORM, .imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, - .imageExtent = {width, height}, + .imageExtent = {new_width, new_height}, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, @@ -63,7 +69,6 @@ void Swapchain::resize(uint32_t new_width, uint32_t new_height) { &swapchain_create_info, nullptr, &swapchain)); - // images uint32_t image_count = 0; CHECK_VULKAN(vkGetSwapchainImagesKHR( @@ -133,12 +138,21 @@ void Swapchain::resize(uint32_t new_width, uint32_t new_height) { {width, height}, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE); + VmaAllocationCreateInfo { + .flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT, + .usage = VMA_MEMORY_USAGE_AUTO, + }); } -Swapchain::Swapchain( - GLFWwindow *window, - iris::Device device) : device(device), window(window) +Swapchain::Swapchain(GLFWwindow *window, + iris::Device device, + uint32_t width, + uint32_t height) + : device(device) + , window(window) + , width(width) + , height(height) + , cmd_buf(device.create_command_buffer()) { if (!glfwVulkanSupported()) { std::cerr << "GLFW failed to find Vulkan support" << std::endl; @@ -146,6 +160,7 @@ Swapchain::Swapchain( abort(); } + std::cerr << "Creating surface" << std::endl; // Create the surface CHECK_VULKAN(glfwCreateWindowSurface( device.instance, @@ -242,6 +257,23 @@ Swapchain::Swapchain( // Create swapchain and image/imageview/framebuffers this->resize(width, height); + // Create semaphores + for (uint32_t i = 0; i < SWAPCHAIN_IMAGE_COUNT; i++) { + VkSemaphoreCreateInfo semaphore_info = { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + CHECK_VULKAN(vkCreateSemaphore( + device.device, + &semaphore_info, + nullptr, + &image_available_semaphores[i])); + CHECK_VULKAN(vkCreateSemaphore( + device.device, + &semaphore_info, + nullptr, + &render_finished_semaphores[i])); + } + // Create descriptor pool VkDescriptorPoolSize pool_size = { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, @@ -260,6 +292,15 @@ Swapchain::Swapchain( &descriptor_pool)); // initialize ImGui + imgui_context = ImGui::CreateContext(); + ImGui::SetCurrentContext(imgui_context); + + ImGuiIO &io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.FontDefault = io.Fonts->AddFontFromFileTTF( + "assets/Roboto-Regular.ttf", + 16.0f); + ImGui_ImplGlfw_InitForVulkan(window, true); ImGui_ImplVulkan_InitInfo init_info = { .Instance = device.instance, @@ -268,28 +309,222 @@ Swapchain::Swapchain( .QueueFamily = device.main_queue_family_index, .Queue = device.graphics_queue, .DescriptorPool = descriptor_pool, + .RenderPass = render_pass, .MinImageCount = SWAPCHAIN_IMAGE_COUNT, .ImageCount = SWAPCHAIN_IMAGE_COUNT, .MSAASamples = VK_SAMPLE_COUNT_1_BIT, - - // optional - .CheckVkResultFn = [](const VkResult err) { CHECK_VULKAN(err); }, }; ImGui_ImplVulkan_Init(&init_info); } +void Swapchain::start_frame() { + VkCommandBufferBeginInfo begin_info = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }; + CHECK_VULKAN(vkBeginCommandBuffer(cmd_buf.buffer, &begin_info)); +} + +void Swapchain::display(Texture2D& texture) { + std::cerr << "Displaying" << std::endl; + VkResult present_result = vkAcquireNextImageKHR( + device.device, + swapchain, + UINT64_MAX, + image_available_semaphores[semaphore_index], + VK_NULL_HANDLE, + &frame_index); + switch (present_result) { + case VK_ERROR_OUT_OF_DATE_KHR: + case VK_SUBOPTIMAL_KHR: + needs_recreate = true; + return; + case VK_SUCCESS: + break; + default: + CHECK_VULKAN(present_result); + } + + std::cerr << "starting image layout transition" << std::endl; + // Command buffer begins at the very start of the frame + // Blit the texture to the swapchain image + { + // swapchain image layout, undefined -> transfer dst + VkImageMemoryBarrier swapchain_image_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = swapchain_images[frame_index], + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + // src image layout, whatever -> transfer src + VkImageMemoryBarrier src_image_barrier = { + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = texture->layout, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = texture->image, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .levelCount = 1, + .layerCount = 1, + }, + }; + VkImageMemoryBarrier barriers[2] = {swapchain_image_barrier, src_image_barrier}; + std::cerr << "starting image layout transition of 2" << std::endl; + vkCmdPipelineBarrier( + cmd_buf.buffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + 0, nullptr, + 0, nullptr, + 2, barriers); + + // Blit the texture to the swapchain image + VkImageBlit blit = { + .srcSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .srcOffsets = { + {0, 0, 0}, + {static_cast<int32_t>(texture->extent.width), static_cast<int32_t>(texture->extent.height), 1}, + }, + .dstSubresource = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .dstOffsets = { + {0, 0, 0}, + {static_cast<int32_t>(width), static_cast<int32_t>(height), 1}, + }, + }; + std::cerr << "Starting blit" << std::endl; + vkCmdBlitImage( + cmd_buf.buffer, + texture->image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + swapchain_images[frame_index], + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &blit, + VK_FILTER_NEAREST); + + // swapchain image layout, transfer dst -> color attachment + // re-use the swapchain_image_barrier + swapchain_image_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + swapchain_image_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + swapchain_image_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + swapchain_image_barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + vkCmdPipelineBarrier( + cmd_buf.buffer, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + 0, + 0, nullptr, + 0, nullptr, + 1, &swapchain_image_barrier); + } + + std::cerr << "starting render pass" << std::endl; + // Render + VkRenderPassBeginInfo render_pass_begin_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = render_pass, + .framebuffer = framebuffers[frame_index], + .renderArea = { + .offset = {0, 0}, + .extent = {width, height}, + }, + .clearValueCount = 0, + }; + vkCmdBeginRenderPass( + cmd_buf.buffer, + &render_pass_begin_info, + VK_SUBPASS_CONTENTS_INLINE); + std::cerr << "rendering imgui" << std::endl; + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmd_buf.buffer); + vkCmdEndRenderPass(cmd_buf.buffer); + + // End command buffer and submit + CHECK_VULKAN(vkEndCommandBuffer(cmd_buf.buffer)); + { + VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + VkSubmitInfo submit_info = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &image_available_semaphores[semaphore_index], + .pWaitDstStageMask = &wait_stage, + .commandBufferCount = 1, + .pCommandBuffers = &cmd_buf.buffer, + .signalSemaphoreCount = 1, + .pSignalSemaphores = &render_finished_semaphores[semaphore_index], + }; + CHECK_VULKAN(vkQueueSubmit( + cmd_buf.queue, + 1, + &submit_info, + VK_NULL_HANDLE)); + } + + // Present the image to the swapchain + VkPresentInfoKHR present_info = { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = &render_finished_semaphores[semaphore_index], + .swapchainCount = 1, + .pSwapchains = &swapchain, + .pImageIndices = &frame_index, + }; + present_result = vkQueuePresentKHR(device.graphics_queue, &present_info); + + switch (present_result) { + case VK_ERROR_OUT_OF_DATE_KHR: + case VK_SUBOPTIMAL_KHR: + needs_recreate = true; + case VK_SUCCESS: + break; + default: + CHECK_VULKAN(present_result); + } + + // Rotate the semaphore index + semaphore_index = (semaphore_index + 1) % SWAPCHAIN_IMAGE_COUNT; +} + Swapchain::~Swapchain() { ImGui_ImplVulkan_Shutdown(); for (uint32_t i = 0; i < SWAPCHAIN_IMAGE_COUNT; i++) { vkDestroyFramebuffer(device.device, framebuffers[i], nullptr); vkDestroyImageView(device.device, swapchain_image_views[i], nullptr); + vkDestroySemaphore(device.device, image_available_semaphores[i], nullptr); + vkDestroySemaphore(device.device, render_finished_semaphores[i], nullptr); } vkDestroyDescriptorPool(device.device, descriptor_pool, nullptr); vkDestroySwapchainKHR(device.device, swapchain, nullptr); vkDestroyRenderPass(device.device, render_pass, nullptr); vkDestroySurfaceKHR(device.instance, surface, nullptr); + + upload_texture.reset(); } } // namespace iris
\ No newline at end of file diff --git a/src/vulkan_swapchain.h b/src/vulkan_swapchain.h index 509753f..b15028f 100644 --- a/src/vulkan_swapchain.h +++ b/src/vulkan_swapchain.h @@ -26,11 +26,20 @@ struct Swapchain { VkImage swapchain_images[SWAPCHAIN_IMAGE_COUNT]; VkImageView swapchain_image_views[SWAPCHAIN_IMAGE_COUNT]; VkFramebuffer framebuffers[SWAPCHAIN_IMAGE_COUNT]; + VkSemaphore image_available_semaphores[SWAPCHAIN_IMAGE_COUNT]; + VkSemaphore render_finished_semaphores[SWAPCHAIN_IMAGE_COUNT]; + CommandBuffer cmd_buf; Texture2D upload_texture; + ImGuiContext *imgui_context = nullptr; + uint32_t semaphore_index = 0; + uint32_t frame_index = 0; + void resize(uint32_t new_width, uint32_t new_height); - Swapchain(GLFWwindow *window, iris::Device device); + void start_frame(); + void display(Texture2D &texture); + Swapchain(GLFWwindow *window, iris::Device device, uint32_t width, uint32_t height); ~Swapchain(); }; |