From e12ca33626bdadedc3158cb69f2a4d2f9bbeeeb0 Mon Sep 17 00:00:00 2001 From: Chuyan Zhang Date: Fri, 6 Sep 2024 01:50:30 -0700 Subject: setup swapchain --- .gitignore | 5 +- CMakeLists.txt | 12 +- ext/imgui.cmake | 2 + src/app.cpp | 44 ++++++-- src/vulkan_helper.cpp | 145 ++++++++++++++++++++++++ src/vulkan_helper.h | 30 +++++ src/vulkan_swapchain.cpp | 279 +++++++++++++++++++++++++++++++++++++++++++++++ src/vulkan_swapchain.h | 34 ++++++ 8 files changed, 536 insertions(+), 15 deletions(-) create mode 100644 src/vulkan_helper.cpp create mode 100644 src/vulkan_helper.h create mode 100644 src/vulkan_swapchain.cpp create mode 100644 src/vulkan_swapchain.h diff --git a/.gitignore b/.gitignore index 6eb228f..01b5580 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,7 @@ .vscode/ # CMake build directory -build/ \ No newline at end of file +build/ + +# clangd config +.clangd \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 05e821f..0cedde2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,10 +27,16 @@ file(GLOB_RECURSE SOURCES "${SRC_DIR}/*.cpp") # Add executable from your source files add_executable(IrisRenderer ${SOURCES}) -find_package(glfw3 REQUIRED) +# find_package(glfw3 REQUIRED) +# target_link_libraries(IrisRenderer PRIVATE glfw) + +# find_package(Vulkan REQUIRED) +# target_link_libraries(IrisRenderer PRIVATE Vulkan::Vulkan) # Link external libraries to your project -target_link_libraries(IrisRenderer PRIVATE argparse imgui tinygltf tinyobjloader glfw) +target_link_libraries(IrisRenderer PRIVATE + argparse imgui tinygltf tinyobjloader + glfw vulkan dl pthread X11 Xxf86vm Xrandr Xi) # Optional: Include additional compiler options or flags -target_compile_options(IrisRenderer PRIVATE -Wall -Wextra) +target_compile_options(IrisRenderer PRIVATE -Wall -Wextra -Wno-missing-field-initializers) diff --git a/ext/imgui.cmake b/ext/imgui.cmake index 42d7c5d..8f48df1 100644 --- a/ext/imgui.cmake +++ b/ext/imgui.cmake @@ -3,8 +3,10 @@ add_library(imgui STATIC ${IMGUI_DIR}/imgui.cpp ${IMGUI_DIR}/imgui_demo.cpp ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_tables.cpp ${IMGUI_DIR}/imgui_widgets.cpp ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp + ${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp ) target_include_directories(imgui PUBLIC diff --git a/src/app.cpp b/src/app.cpp index 7ef2310..b3df2a5 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -1,21 +1,45 @@ +#include "vulkan_swapchain.h" + #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_vulkan.h" #include "argparse/argparse.hpp" + +#define GLFW_INCLUDE_VULKAN #include +#include #include +#include +#include + +std::unique_ptr start_up(int width, int height) { + auto glfw_extensions = get_glfw_instance_extensions(); + // std::vector glfw_extensions = { + // "VK_KHR_surface", + // }; + // for (const auto& extension : glfw_extensions) { + // std::cerr << "GLFW extension: " << extension << std::endl; + // } + iris::Device device({}, glfw_extensions); -GLFWwindow *start_up(int width, int height) { - return nullptr; + 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(window, device); } void main_loop(GLFWwindow *window) { - + (void) window; } -void shut_down(GLFWwindow *window) { +void shut_down(std::unique_ptr& swapchain) { + ImGui_ImplVulkan_Shutdown(); + glfwDestroyWindow(swapchain->window); + glfwTerminate(); } int main(int argc, char** argv) { @@ -51,13 +75,11 @@ int main(int argc, char** argv) { return -1; } - auto window = start_up(window_width, window_height); - if (window == nullptr) { - return -2; + auto swapchain = start_up(window_width, window_height); + while (!glfwWindowShouldClose(swapchain->window)) { + main_loop(swapchain->window); } - while (!glfwWindowShouldClose(window)) { - main_loop(window); - } - shut_down(window); + + shut_down(swapchain); return 0; } \ No newline at end of file diff --git a/src/vulkan_helper.cpp b/src/vulkan_helper.cpp new file mode 100644 index 0000000..84a2b6d --- /dev/null +++ b/src/vulkan_helper.cpp @@ -0,0 +1,145 @@ +#include "vulkan_helper.h" +#include "vulkan/vulkan_core.h" +#include +#include +#include + +namespace iris { + +Device::Device(std::vector layers, std::vector instance_extensions) { + VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "IrisRenderer", + .applicationVersion = VK_MAKE_VERSION(1, 0, 0), + .pEngineName = "No Engine", + .engineVersion = VK_MAKE_VERSION(1, 0, 0), + .apiVersion = VK_API_VERSION_1_0, + }; + + // Create the Vulkan instance + uint32_t enabled_layer_count = layers.size(); + std::vector layers_cstr; + for (const auto& layer : layers) { + layers_cstr.push_back(layer.c_str()); + } + + uint32_t enabled_extension_count = instance_extensions.size(); + std::vector instance_extensions_cstr; + for (const auto& extension : instance_extensions) { + instance_extensions_cstr.push_back(extension.c_str()); + } + + VkInstanceCreateInfo instance_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + .enabledLayerCount = enabled_layer_count, + .ppEnabledLayerNames = enabled_layer_count == 0 ? VK_NULL_HANDLE : layers_cstr.data(), + .enabledExtensionCount = enabled_extension_count, + .ppEnabledExtensionNames = enabled_extension_count == 0 ? VK_NULL_HANDLE : instance_extensions_cstr.data(), + }; + CHECK_VULKAN(vkCreateInstance( + &instance_info, + VK_NULL_HANDLE, + &instance)); + + // Enumerate and select the physical device + uint32_t physical_device_count = 0; + CHECK_VULKAN(vkEnumeratePhysicalDevices( + instance, + &physical_device_count, + VK_NULL_HANDLE)); + std::vector physical_devices(physical_device_count); + CHECK_VULKAN(vkEnumeratePhysicalDevices( + instance, + &physical_device_count, + physical_devices.data())); + // For now, just select the first physical device, optionally check capabilities of the device + physical_device = physical_devices[0]; + { + uint32_t device_extension_count = 0; + CHECK_VULKAN(vkEnumerateDeviceExtensionProperties( + physical_device, + VK_NULL_HANDLE, + &device_extension_count, + VK_NULL_HANDLE)); + std::vector device_extensions(device_extension_count); + CHECK_VULKAN(vkEnumerateDeviceExtensionProperties( + physical_device, + VK_NULL_HANDLE, + &device_extension_count, + device_extensions.data())); + + bool has_raytracing = false; + bool has_acceleration_structure = false; + for (const auto& extension : device_extensions) { + if (std::string(extension.extensionName) == VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME) { + has_raytracing = true; + } + if (std::string(extension.extensionName) == VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) { + has_acceleration_structure = true; + } + } + + if (!has_raytracing || !has_acceleration_structure) { + // TODO throw an exception + std::cerr << "Physical device does not support ray tracing extensions" << std::endl; + abort(); + } + } + + // Create the logical device + float queue_priority = 1.0f; + main_queue_family_index = 0; // TODO: query capabilities to find a proper queue index + VkDeviceQueueCreateInfo queue_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = main_queue_family_index, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }; + + VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration_structure_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR, + .accelerationStructure = VK_TRUE, + }; + VkPhysicalDeviceRayTracingPipelineFeaturesKHR raytracing_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR, + .pNext = &acceleration_structure_features, + .rayTracingPipeline = VK_TRUE, + }; + VkPhysicalDeviceFeatures2 device_features = { + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, + .pNext = &raytracing_features, + .features { + .samplerAnisotropy = VK_TRUE, + } + }; + + constexpr char *device_extensions[] = { + VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME, + VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, + VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, + VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME + }; + VkDeviceCreateInfo device_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = &device_features, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_info, + .enabledExtensionCount = sizeof(device_extensions) / sizeof(device_extensions[0]), + .ppEnabledExtensionNames = device_extensions, + }; + CHECK_VULKAN(vkCreateDevice( + physical_device, + &device_info, + VK_NULL_HANDLE, + &device)); + + // Get the graphics queue + vkGetDeviceQueue( + device, + main_queue_family_index, + 0, + &graphics_queue); +} + +} // namespace iris \ No newline at end of file diff --git a/src/vulkan_helper.h b/src/vulkan_helper.h new file mode 100644 index 0000000..089a09f --- /dev/null +++ b/src/vulkan_helper.h @@ -0,0 +1,30 @@ +#include +#include +#include +#include + +#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(); \ + } \ + } while (0) + +namespace iris { + +struct Device { + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + uint32_t main_queue_family_index; + VkQueue graphics_queue; + + Device( + std::vector layers, + std::vector instance_extensions); +}; + +} // namespace iris \ No newline at end of file diff --git a/src/vulkan_swapchain.cpp b/src/vulkan_swapchain.cpp new file mode 100644 index 0000000..651b558 --- /dev/null +++ b/src/vulkan_swapchain.cpp @@ -0,0 +1,279 @@ +#include "vulkan_swapchain.h" + +#include "GLFW/glfw3.h" +#include "imgui_impl_glfw.h" +#include "imgui_impl_vulkan.h" +#include "vulkan/vulkan_core.h" + +#include +#include +#include +#include +#include + +std::vector get_glfw_instance_extensions() { + uint32_t extension_count = 0; + const char **extensions = glfwGetRequiredInstanceExtensions(&extension_count); + + std::vector result; + result.reserve(extension_count); + for (uint32_t i = 0; i < extension_count; i++) { + result.push_back(extensions[i]); + } + return result; +} + +namespace iris { + +Swapchain::Swapchain( + GLFWwindow *window, + iris::Device device) : device(device), window(window) +{ + if (!glfwVulkanSupported()) { + std::cerr << "GLFW failed to find Vulkan support" << std::endl; + // TODO throw an exception + abort(); + } + + // Create the surface + CHECK_VULKAN(glfwCreateWindowSurface( + device.instance, + window, + VK_NULL_HANDLE, + &surface)); + + // Optionally check surface capabilities + { + VkBool32 supported = VK_FALSE; + CHECK_VULKAN(vkGetPhysicalDeviceSurfaceSupportKHR( + device.physical_device, + device.main_queue_family_index, + surface, + &supported)); + if (supported != VK_TRUE) { + // TODO throw an exception + std::cerr << "Surface does not support presentation" << std::endl; + abort(); + } + + VkSurfaceCapabilitiesKHR surface_capabilities; + CHECK_VULKAN(vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + device.physical_device, + surface, + &surface_capabilities)); + + if (surface_capabilities.maxImageCount < SWAPCHAIN_IMAGE_COUNT) { + // TODO throw an exception + std::cerr << "Surface does not support enough images" << std::endl; + abort(); + } + } + + // Create render pass + VkAttachmentDescription2 attachment = { + .sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2, + .format = VK_FORMAT_B8G8R8A8_UNORM, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }; + VkAttachmentReference2 color_attachment = { + .sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2, + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + VkSubpassDescription2 subpass = { + .sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2, + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment, + }; + VkSubpassDependency2 dependencies[2] = { + { + .sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2, + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + }, + { + .sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2, + .srcSubpass = 0, + .dstSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + }, + }; + + VkRenderPassCreateInfo2 render_pass_create_info = { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2, + .attachmentCount = 1, + .pAttachments = &attachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 2, + .pDependencies = dependencies, + }; + CHECK_VULKAN(vkCreateRenderPass2( + device.device, + &render_pass_create_info, + nullptr, + &render_pass)); + + // Create swapchain and image/imageview/framebuffers + // TODO: move this into `resize` to support resizing + { + // swapchain + int i_width, i_height; + glfwGetFramebufferSize(window, &i_width, &i_height); + width = (uint32_t) i_width; + height = (uint32_t) i_height; + + 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}, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, + .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR, + .clipped = VK_TRUE, + .oldSwapchain = VK_NULL_HANDLE, + }; + CHECK_VULKAN(vkCreateSwapchainKHR( + device.device, + &swapchain_create_info, + nullptr, + &swapchain)); + + // images + uint32_t image_count = 0; + CHECK_VULKAN(vkGetSwapchainImagesKHR( + device.device, + swapchain, + &image_count, + nullptr)); + if (image_count > SWAPCHAIN_IMAGE_COUNT) { + // TODO throw an exception + std::cerr << "Swapchain image count is greater than expected" << std::endl; + abort(); + } + CHECK_VULKAN(vkGetSwapchainImagesKHR( + device.device, + swapchain, + &image_count, + swapchain_images)); + + // image views + for (uint32_t i = 0; i < image_count; i++) { + VkImageViewCreateInfo image_view_create_info = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = swapchain_images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = VK_FORMAT_B8G8R8A8_UNORM, + .components = { + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange = { + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }; + + CHECK_VULKAN(vkCreateImageView( + device.device, + &image_view_create_info, + nullptr, + &swapchain_image_views[i])); + } + + // framebuffers + for (uint32_t i = 0; i < image_count; i++) { + VkFramebufferCreateInfo frame_buffer_create_info = { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = render_pass, + .attachmentCount = 1, + .pAttachments = &swapchain_image_views[i], + .width = width, + .height = height, + .layers = 1, + }; + CHECK_VULKAN(vkCreateFramebuffer( + device.device, + &frame_buffer_create_info, + nullptr, + &framebuffers[i])); + } + } + + + // Create descriptor pool + VkDescriptorPoolSize pool_size = { + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorCount = 1, + }; + VkDescriptorPoolCreateInfo pool_info = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, + .maxSets = 1, + .poolSizeCount = 1, + .pPoolSizes = &pool_size, + }; + CHECK_VULKAN(vkCreateDescriptorPool( + device.device, + &pool_info, + nullptr, + &descriptor_pool)); + + // initialize ImGui + ImGui_ImplGlfw_InitForVulkan(window, true); + ImGui_ImplVulkan_InitInfo init_info = { + .Instance = device.instance, + .PhysicalDevice = device.physical_device, + .Device = device.device, + .QueueFamily = device.main_queue_family_index, + .Queue = device.graphics_queue, + .DescriptorPool = descriptor_pool, + .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); +} + +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); + } + + vkDestroyDescriptorPool(device.device, descriptor_pool, nullptr); + vkDestroySwapchainKHR(device.device, swapchain, nullptr); + vkDestroyRenderPass(device.device, render_pass, nullptr); + vkDestroySurfaceKHR(device.instance, surface, nullptr); +} + +} // namespace iris \ No newline at end of file diff --git a/src/vulkan_swapchain.h b/src/vulkan_swapchain.h new file mode 100644 index 0000000..99405b6 --- /dev/null +++ b/src/vulkan_swapchain.h @@ -0,0 +1,34 @@ +#include "imgui_impl_glfw.h" +#include "imgui_impl_vulkan.h" +#include "vulkan_helper.h" +#include "vulkan/vulkan_core.h" +#include +#include +#include +#include + +std::vector get_glfw_instance_extensions(); + +namespace iris { + +struct Swapchain { + iris::Device device; + + VkSurfaceKHR surface = VK_NULL_HANDLE; + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + VkRenderPass render_pass = VK_NULL_HANDLE; + VkDescriptorPool descriptor_pool = VK_NULL_HANDLE; + GLFWwindow *window = nullptr; + uint32_t width = -1; + uint32_t height = -1; + + static constexpr uint32_t SWAPCHAIN_IMAGE_COUNT = 3; + VkImage swapchain_images[SWAPCHAIN_IMAGE_COUNT]; + VkImageView swapchain_image_views[SWAPCHAIN_IMAGE_COUNT]; + VkFramebuffer framebuffers[SWAPCHAIN_IMAGE_COUNT]; + + Swapchain(GLFWwindow *window, iris::Device device); + ~Swapchain(); +}; + +} // namespace iris \ No newline at end of file -- cgit v1.2.3-70-g09d2