From ed35935f7e8dbb643854315bac1214b6855e9655 Mon Sep 17 00:00:00 2001 From: Draklaw Date: Mon, 20 Dec 2021 22:32:59 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 2 + CMakeLists.txt | 57 +++ README.md | 0 shaders/shader.frag | 9 + shaders/shader.vert | 20 + src/Logger.cpp | 41 ++ src/Logger.h | 39 ++ src/VkExpe.cpp | 87 ++++ src/VkExpe.h | 33 ++ src/VulkanTutorial.cpp | 972 +++++++++++++++++++++++++++++++++++++++++ src/VulkanTutorial.h | 106 +++++ src/main.cpp | 19 + src/utils.cpp | 18 + src/utils.h | 34 ++ 14 files changed, 1437 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 shaders/shader.frag create mode 100644 shaders/shader.vert create mode 100644 src/Logger.cpp create mode 100644 src/Logger.h create mode 100644 src/VkExpe.cpp create mode 100644 src/VkExpe.h create mode 100644 src/VulkanTutorial.cpp create mode 100644 src/VulkanTutorial.h create mode 100644 src/main.cpp create mode 100644 src/utils.cpp create mode 100644 src/utils.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00a6291 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.vscode +/build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4258fbe --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.0.0) +project(vk_expe VERSION 0.1.0) + +find_package(Eigen3 3.3 REQUIRED) +find_package(Vulkan REQUIRED) +find_package(SDL2 2.0.18 REQUIRED) + +include(CTest) +enable_testing() + +function(add_shaders TARGET) + foreach(SRC ${ARGN}) + set(DST "${CMAKE_CURRENT_BINARY_DIR}/${SRC}.spv") + list(APPEND SPVS "${DST}") + get_filename_component(DST_DIR "${DST}" DIRECTORY) + file(MAKE_DIRECTORY "${DST_DIR}") + add_custom_command( + OUTPUT "${DST}" + COMMAND "$ENV{VULKAN_SDK}/bin/glslc" "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}" -o "${DST}" + MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}" + ) + endforeach() + + add_custom_target( + ${TARGET}_shaders + DEPENDS ${SPVS} + ) + + add_dependencies(${TARGET} ${TARGET}_shaders) +endfunction() + +add_executable(vk_expe + src/main.cpp + src/utils.cpp + src/Logger.cpp + src/VkExpe.cpp + src/VulkanTutorial.cpp +) + +add_shaders(vk_expe + shaders/shader.vert + shaders/shader.frag +) + +target_compile_features(vk_expe + PUBLIC cxx_std_17 +) + +target_link_libraries(vk_expe + PUBLIC Eigen3::Eigen + PUBLIC Vulkan::Vulkan + PUBLIC SDL2::SDL2 +) + +set(CPACK_PROJECT_NAME ${PROJECT_NAME}) +set(CPACK_PROJECT_VERSION ${PROJECT_VERSION}) +include(CPack) diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/shaders/shader.frag b/shaders/shader.frag new file mode 100644 index 0000000..7c5b0e7 --- /dev/null +++ b/shaders/shader.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/shaders/shader.vert b/shaders/shader.vert new file mode 100644 index 0000000..f5b2f8d --- /dev/null +++ b/shaders/shader.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +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 = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +} diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..3bd5a92 --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,41 @@ +#include "Logger.h" + +#include + + +LogStream::LogStream(std::ostream& out) + : m_out(out) { +} + +LogStream::~LogStream() { + m_out << "\033[m\n" << std::flush; +} + + +Logger::Logger() { +} + +Logger::~Logger() { +} + +LogStream Logger::error() { + std::clog << "\033[31m"; + return LogStream(std::clog); +} + +LogStream Logger::warning() { + std::clog << "\033[33m"; + return LogStream(std::clog); +} + +LogStream Logger::info() { + // std::clog << "\033[37m"; + return LogStream(std::clog); +} + +LogStream Logger::debug() { + std::clog << "\033[2m"; + return LogStream(std::clog); +} + +Logger logger; diff --git a/src/Logger.h b/src/Logger.h new file mode 100644 index 0000000..27fedd6 --- /dev/null +++ b/src/Logger.h @@ -0,0 +1,39 @@ +#pragma once + +#include + + +class LogStream { +public: + LogStream(std::ostream& out); + LogStream(const LogStream&) = delete; + ~LogStream(); + + LogStream& operator=(const LogStream&) = delete; + + template + LogStream& operator<<(T&& value) { + m_out << std::forward(value); + return *this; + } + +private: + std::ostream& m_out; +}; + + +class Logger { +public: + Logger(); + Logger(const Logger&) = delete; + ~Logger(); + + Logger& operator=(const Logger&) = delete; + + LogStream error(); + LogStream warning(); + LogStream info(); + LogStream debug(); +}; + +extern Logger logger; diff --git a/src/VkExpe.cpp b/src/VkExpe.cpp new file mode 100644 index 0000000..9e5355b --- /dev/null +++ b/src/VkExpe.cpp @@ -0,0 +1,87 @@ +#include "VkExpe.h" + +#include "utils.h" +#include "Logger.h" + +#include +#include + + +void SdlWindowDeleter::operator()(SDL_Window* window) const { + SDL_DestroyWindow(window); +} + + +VkExpe::VkExpe(int argc, char** argv) { +} + +VkExpe::~VkExpe() { + shutdown(); +} + +void VkExpe::initialize() { + SDL_Init(SDL_INIT_EVENTS | SDL_INIT_VIDEO); + + auto const window = SDL_CreateWindow( + "vk_expe", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + 800, 600, + SDL_WINDOW_VULKAN + | SDL_WINDOW_ALLOW_HIGHDPI + | SDL_WINDOW_SHOWN + | SDL_WINDOW_RESIZABLE + ); + m_window = WindowUP( + window + ); + if(!m_window) + throw std::runtime_error("failed to create window"); + + m_vulkan.initialize(m_window.get()); +} + +void VkExpe::shutdown() { + m_vulkan.shutdown(); + + m_window.reset(); + + if (SDL_WasInit(0)) + SDL_Quit(); +} + +void VkExpe::run() { + if(m_running) + throw std::runtime_error("calling run while already running"); + + initialize(); + + m_running = true; + auto running_guard = make_guard([this] { + m_running = false; + }); + + SDL_Event event; + while(m_running) { + while(SDL_PollEvent(&event)) { + switch(event.type) { + case SDL_QUIT: + m_running = false; + break; + case SDL_KEYDOWN: { + if(event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) + m_running = false; + break; + } + case SDL_WINDOWEVENT: { + switch(event.window.event) { + case SDL_WINDOWEVENT_RESIZED: { + m_vulkan.invalidate_swapchain(); + } + } + } + } + } + + m_vulkan.draw_frame(); + } +} diff --git a/src/VkExpe.h b/src/VkExpe.h new file mode 100644 index 0000000..b145a1f --- /dev/null +++ b/src/VkExpe.h @@ -0,0 +1,33 @@ +#pragma once + +#include "VulkanTutorial.h" + +#include + +#include + + +struct SdlWindowDeleter { + void operator()(SDL_Window* window) const; +}; +using WindowUP = std::unique_ptr; + + +class VkExpe { +public: + VkExpe(int argc, char** argv); + VkExpe(const VkExpe&) = delete; + ~VkExpe(); + + VkExpe& operator=(const VkExpe&) = delete; + + void initialize(); + void shutdown(); + + void run(); + +private: + VulkanTutorial m_vulkan; + WindowUP m_window; + bool m_running = false; +}; diff --git a/src/VulkanTutorial.cpp b/src/VulkanTutorial.cpp new file mode 100644 index 0000000..1ce168e --- /dev/null +++ b/src/VulkanTutorial.cpp @@ -0,0 +1,972 @@ +#include "VulkanTutorial.h" + +#include "utils.h" +#include "Logger.h" + +#include + +#include +#include +#include +#include +#include +#include + + +PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT = nullptr; +PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT = nullptr; + + +VulkanTutorial::VulkanTutorial() { +} + +VulkanTutorial::~VulkanTutorial() { + shutdown(); +} + +void VulkanTutorial::initialize(SDL_Window* window) { + m_window = window; + + create_instance(); + create_surface(); + find_physical_device(); + create_device(); + create_swapchain(); + create_render_pass(); + create_graphic_pipeline(); + create_framebuffers(); + create_command_pool(); + create_command_buffers(); + create_sync_objects(); +} + +void VulkanTutorial::shutdown() { + if(!m_instance) + return; + + vkDeviceWaitIdle(m_device); + + + for(VkFence fence: m_in_flight_fence) + vkDestroyFence(m_device, fence, nullptr); + m_in_flight_fence.clear(); + + for(VkSemaphore semaphore: m_render_finished_semaphores) + vkDestroySemaphore(m_device, semaphore, nullptr); + m_render_finished_semaphores.clear(); + + for(VkSemaphore semaphore: m_image_available_semaphores) + vkDestroySemaphore(m_device, semaphore, nullptr); + m_image_available_semaphores.clear(); + + if(m_command_pool != VK_NULL_HANDLE) { + vkDestroyCommandPool(m_device, m_command_pool, nullptr); + m_command_pool = VK_NULL_HANDLE; + } + + cleanup_swap_chain(); + + if(m_device) { + vkDestroyDevice(m_device, nullptr); + m_device = nullptr; + } + + if(m_surface) { + vkDestroySurfaceKHR(m_instance, m_surface, nullptr); + m_surface = nullptr; + } + + if(m_debug_messenger != VK_NULL_HANDLE) { + vkeDestroyDebugUtilsMessengerEXT( + m_instance, m_debug_messenger, nullptr); + m_debug_messenger = VK_NULL_HANDLE; + } + + vkDestroyInstance(m_instance, nullptr); + m_instance = nullptr; + m_physical_device = nullptr; + + m_window = nullptr; +} + +void VulkanTutorial::draw_frame() { + vkWaitForFences(m_device, 1, &m_in_flight_fence[m_frame_index], VK_TRUE, UINT64_MAX); + + uint32_t image_index; + VkResult result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, m_image_available_semaphores[m_frame_index], VK_NULL_HANDLE, &image_index); + if(result == VK_ERROR_OUT_OF_DATE_KHR) { + recreate_swap_chain(); + return; + } + else if(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) + throw std::runtime_error("failed to acquire swapchain image"); + + if(m_images_in_flight[image_index] != VK_NULL_HANDLE) + vkWaitForFences(m_device, 1, &m_images_in_flight[image_index], VK_TRUE, UINT64_MAX); + m_images_in_flight[image_index] = m_in_flight_fence[image_index]; + + VkSemaphore wait_semaphores[] = { + m_image_available_semaphores[m_frame_index], + }; + VkPipelineStageFlags stages[] = { + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + }; + VkSemaphore signal_semaphores[] = { + m_render_finished_semaphores[m_frame_index], + }; + VkSubmitInfo submit_info { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .waitSemaphoreCount = 1, + .pWaitSemaphores = wait_semaphores, + .pWaitDstStageMask = stages, + .commandBufferCount = 1, + .pCommandBuffers = &m_command_buffers[image_index], + .signalSemaphoreCount = 1, + .pSignalSemaphores = signal_semaphores, + }; + + vkResetFences(m_device, 1, &m_in_flight_fence[m_frame_index]); + if(vkQueueSubmit(m_graphic_queue, 1, &submit_info, m_in_flight_fence[m_frame_index])) + throw std::runtime_error("failed to submit draw command buffer"); + + VkPresentInfoKHR present_info { + .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, + .waitSemaphoreCount = 1, + .pWaitSemaphores = signal_semaphores, + .swapchainCount = 1, + .pSwapchains = &m_swapchain, + .pImageIndices = &image_index, + .pResults = nullptr, + }; + + result = vkQueuePresentKHR(m_presentation_queue, &present_info); + if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || m_invalid_swapchain) + recreate_swap_chain(); + if(result != VK_SUCCESS && result != VK_ERROR_OUT_OF_DATE_KHR && result != VK_SUBOPTIMAL_KHR) + throw std::runtime_error("failed to present swapchain image"); + + m_frame_index = (m_frame_index + 1) % MAX_FRAMES_IN_FLIGHT; +} + +void VulkanTutorial::invalidate_swapchain() { + m_invalid_swapchain = true; +} + +std::vector VulkanTutorial::available_extensions() { + uint32_t count; + vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + + std::vector extensions(count); + vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data()); + + return extensions; +} + +std::vector VulkanTutorial::available_layers() { + uint32_t count; + vkEnumerateInstanceLayerProperties(&count, nullptr); + + std::vector layers(count); + vkEnumerateInstanceLayerProperties(&count, layers.data()); + + return layers; +} + +VKAPI_ATTR VkBool32 VKAPI_CALL VulkanTutorial::print_debug_message( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* data, + void* user_data +) { + auto stream = [severity]() { + switch(severity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + return logger.debug(); + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + return logger.info(); + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + return logger.warning(); + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + return logger.error(); + } + return logger.debug(); + }(); + + switch(type) { + case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: + stream << "[vk:general] "; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: + stream << "[vk:validation] "; + break; + case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: + stream << "[vk:performance] "; + break; + } + + stream << data->pMessage; + + if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) + abort(); + + return VK_FALSE; +} + +void VulkanTutorial::create_instance() { + if(m_instance) + return; + + const char* name = "vk_expe"; + const uint32_t version = VK_MAKE_VERSION(0, 1, 0); + + VkApplicationInfo app_info { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = name, + .applicationVersion = version, + .pEngineName = name, + .engineVersion = version, + .apiVersion = VK_API_VERSION_1_1, + }; + + std::vector layers; + std::vector extensions; + + { + unsigned count; + if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, nullptr)) + throw std::runtime_error("failed to get window's vulkan extensions"); + extensions.resize(count); + + if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, extensions.data())) + throw std::runtime_error("failed to get window's vulkan extensions"); + } + + if(m_enableValidation) { + extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + auto const available_layers = VulkanTutorial::available_layers(); + + auto add_layer_if_available = [&](const char* requested_layer) { + if(std::find_if( + available_layers.begin(), + available_layers.end(), + [requested_layer](const VkLayerProperties& layer) { + return std::strcmp(layer.layerName, requested_layer) == 0; + } + ) != available_layers.end()) { + layers.push_back(requested_layer); + } + else { + logger.warning() << "requested layer '" << requested_layer + << "' is not available"; + } + }; + + add_layer_if_available("VK_LAYER_KHRONOS_validation"); + } + + m_debug_info = VkDebugUtilsMessengerCreateInfoEXT { + .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, + .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, + .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, + .pfnUserCallback = &print_debug_message, + .pUserData = nullptr, + }; + + VkInstanceCreateInfo instance_info { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = &m_debug_info, + .pApplicationInfo = &app_info, + .enabledLayerCount = uint32_t(layers.size()), + .ppEnabledLayerNames = layers.data(), + .enabledExtensionCount = uint32_t(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + }; + + logger.debug() << "Requested extensions:"; + for(const char* extension: extensions) + logger.debug() << " " << extension; + logger.debug() << "Requested layers:"; + for(const char* layer: layers) + logger.debug() << " " << layer; + + logger.debug() << "create vulkan instance..."; + if(vkCreateInstance(&instance_info, nullptr, &m_instance) != VK_SUCCESS) + throw std::runtime_error("failed to create vulkan instance"); + + initialize_extension_functions(); + + if(m_enableValidation) { + vkeCreateDebugUtilsMessengerEXT( + m_instance, &m_debug_info, nullptr, &m_debug_messenger); + } +} + +void VulkanTutorial::create_surface() { + if(!SDL_Vulkan_CreateSurface(m_window, m_instance, &m_surface)) + throw std::runtime_error("failed to create surface"); +} + +void VulkanTutorial::find_physical_device() { + auto const physical_devices = get_physical_devices(); + for(auto const physical_device: physical_devices) { + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties(physical_device, &device_properties); + + int graphic_family = -1; + int presentation_family = -1; + + auto const queue_families = get_queue_families(physical_device); + int index = 0; + for(auto const& queue_family: queue_families) { + if(queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) + graphic_family = index; + + VkBool32 support_surface = false; + vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, index, m_surface, &support_surface); + if(support_surface) + presentation_family = index; + + index += 1; + } + + if(graphic_family < 0 || presentation_family < 0) + continue; + + auto const available_extensions = get_device_extensions(physical_device); + auto has_extension = [&available_extensions](const char* required_extension) { + return std::find_if( + available_extensions.begin(), + available_extensions.end(), + [required_extension](const VkExtensionProperties& extension) { + return strcmp(extension.extensionName, required_extension) == 0; + } + ) != available_extensions.end(); + }; + + if(!has_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME)) + continue; + + auto const surface_formats = get_surface_formats(physical_device); + if(surface_formats.empty()) + continue; + + auto const present_modes = get_present_modes(physical_device); + if(present_modes.empty()) + continue; + + logger.info() << "use suitable device: " << device_properties.deviceName; + m_physical_device = physical_device; + m_graphic_queue_family = uint32_t(graphic_family); + m_presentation_queue_family = uint32_t(presentation_family); + + auto const surface_format_it = std::find_if( + surface_formats.begin(), + surface_formats.end(), + [](const VkSurfaceFormatKHR& surface_format) { + return surface_format.format == VK_FORMAT_B8G8R8A8_SRGB + && surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; + } + ); + m_swapchain_format = (surface_format_it != surface_formats.end())? + *surface_format_it: + surface_formats[0]; + logger.debug() << "swapchain format: " << m_swapchain_format.format; + + m_present_mode = VK_PRESENT_MODE_FIFO_KHR; + logger.debug() << "present mode: " << m_present_mode; + + break; + } + + if(!m_physical_device) + throw std::runtime_error("failed to find suitable physical device"); +} + +void VulkanTutorial::create_device() { + std::vector queue_families { + m_graphic_queue_family, + m_presentation_queue_family, + }; + std::sort(queue_families.begin(), queue_families.end()); + queue_families.erase( + std::unique(queue_families.begin(), queue_families.end()), + queue_families.end() + ); + + float queue_priority = 1.0f; + std::vector queue_infos; + for(uint32_t queue_family: queue_families) { + queue_infos.emplace_back(VkDeviceQueueCreateInfo { + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .queueFamilyIndex = queue_family, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }); + } + + VkPhysicalDeviceFeatures device_features { + }; + + std::vector extensions { + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; + + VkDeviceCreateInfo device_info { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .queueCreateInfoCount = uint32_t(queue_infos.size()), + .pQueueCreateInfos = queue_infos.data(), + .enabledExtensionCount = uint32_t(extensions.size()), + .ppEnabledExtensionNames = extensions.data(), + .pEnabledFeatures = &device_features, + }; + + logger.debug() << "create vulkan device..."; + if(vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device) != VK_SUCCESS) + throw std::runtime_error("failed to create logical device"); + + vkGetDeviceQueue(m_device, m_graphic_queue_family, 0, &m_graphic_queue); + vkGetDeviceQueue(m_device, m_presentation_queue_family, 0, &m_presentation_queue); +} + +void VulkanTutorial::create_swapchain() { + VkSurfaceCapabilitiesKHR capabilities; + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, m_surface, &capabilities); + + m_swapchain_extent = capabilities.currentExtent; + if(m_swapchain_extent.width == UINT32_MAX) { + int width; + int height; + SDL_Vulkan_GetDrawableSize(m_window, &width, &height); + + m_swapchain_extent.width = std::clamp( + uint32_t(width), + capabilities.minImageExtent.width, + capabilities.maxImageExtent.width + ); + m_swapchain_extent.height = std::clamp( + uint32_t(height), + capabilities.minImageExtent.height, + capabilities.maxImageExtent.height + ); + } + logger.debug() << "swapchain extent: " << m_swapchain_extent.width + << ", " << m_swapchain_extent.height; + + uint32_t image_count = capabilities.minImageCount + 1; + if(capabilities.maxImageCount != 0) + image_count = std::min(image_count, capabilities.maxImageCount); + logger.debug() << "swapchain image count: " << image_count; + + bool share_image = (m_graphic_queue_family != m_presentation_queue_family); + uint32_t queue_families[] = { + m_graphic_queue_family, + m_presentation_queue_family, + }; + + VkSwapchainCreateInfoKHR swapchain_info { + .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, + .surface = m_surface, + .minImageCount = image_count, + .imageFormat = m_swapchain_format.format, + .imageColorSpace = m_swapchain_format.colorSpace, + .imageExtent = m_swapchain_extent, + .imageArrayLayers = 1, + .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .imageSharingMode = share_image? + VK_SHARING_MODE_CONCURRENT: + VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = share_image? 2u: 0u, + .pQueueFamilyIndices = share_image? queue_families: nullptr, + .preTransform = capabilities.currentTransform, + .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .presentMode = m_present_mode, + .clipped = VK_TRUE, + .oldSwapchain = VK_NULL_HANDLE, + }; + + if(vkCreateSwapchainKHR(m_device, &swapchain_info, nullptr, &m_swapchain) != VK_SUCCESS) + throw std::runtime_error("failed to create swapchain"); + + vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, nullptr); + m_swapchain_images.resize(image_count); + vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, m_swapchain_images.data()); + + m_swapchain_image_views.resize(image_count); + for(size_t index = 0; index < image_count; index += 1) { + VkImageViewCreateInfo view_info { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = m_swapchain_images[index], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = m_swapchain_format.format, + .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, + }, + }; + + if(vkCreateImageView(m_device, &view_info, nullptr, &m_swapchain_image_views[index]) != VK_SUCCESS) + throw std::runtime_error("failed to create swapchain image view"); + } + + m_invalid_swapchain = false; +} + +void VulkanTutorial::create_render_pass() { + VkAttachmentDescription color_attachment { + .format = m_swapchain_format.format, + .samples = VK_SAMPLE_COUNT_1_BIT, + .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, + .storeOp = VK_ATTACHMENT_STORE_OP_STORE, + .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, + .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + }; + + VkAttachmentReference color_attachment_ref = { + .attachment = 0, + .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + }; + + VkSubpassDescription subpass { + .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, + .colorAttachmentCount = 1, + .pColorAttachments = &color_attachment_ref, + }; + + VkSubpassDependency subpass_dep = { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + }; + + VkRenderPassCreateInfo render_pass_info { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, + .attachmentCount = 1, + .pAttachments = &color_attachment, + .subpassCount = 1, + .pSubpasses = &subpass, + .dependencyCount = 1, + .pDependencies = &subpass_dep, + }; + + if(vkCreateRenderPass(m_device, &render_pass_info, nullptr, &m_render_pass) != VK_SUCCESS) + throw std::runtime_error("failed to create render pass"); +} + +void VulkanTutorial::create_graphic_pipeline() { + auto const vertex_shader_module = create_shader_module_from_file("shaders/shader.vert.spv"); + auto const vertex_shader_guard = make_guard([&]{ + vkDestroyShaderModule(m_device, vertex_shader_module, nullptr); + }); + + auto const fragment_shader_module = create_shader_module_from_file("shaders/shader.frag.spv"); + auto const fragment_shader_guard = make_guard([&]{ + vkDestroyShaderModule(m_device, fragment_shader_module, nullptr); + }); + + VkPipelineShaderStageCreateInfo shader_stage_infos[] = { + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_VERTEX_BIT, + .module = vertex_shader_module, + .pName = "main", + }, + { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_FRAGMENT_BIT, + .module = fragment_shader_module, + .pName = "main", + }, + }; + + VkPipelineVertexInputStateCreateInfo vertex_info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .vertexBindingDescriptionCount = 0, + .pVertexBindingDescriptions = nullptr, + .vertexAttributeDescriptionCount = 0, + .pVertexAttributeDescriptions = nullptr, + }; + + VkPipelineInputAssemblyStateCreateInfo assembly_info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + .primitiveRestartEnable = VK_FALSE, + }; + + VkViewport viewport { + .x = 0.0f, + .y = 0.0f, + .width = float(m_swapchain_extent.width), + .height = float(m_swapchain_extent.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + + VkRect2D scissor = { + .offset = { 0, 0 }, + .extent = m_swapchain_extent, + }; + + VkPipelineViewportStateCreateInfo viewport_info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .viewportCount = 1, + .pViewports = &viewport, + .scissorCount = 1, + .pScissors = &scissor, + }; + + VkPipelineRasterizationStateCreateInfo rasterization_info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .depthClampEnable = VK_FALSE, + .rasterizerDiscardEnable = VK_FALSE, + .polygonMode = VK_POLYGON_MODE_FILL, + .cullMode = VK_CULL_MODE_BACK_BIT, + .frontFace = VK_FRONT_FACE_CLOCKWISE, + .depthBiasEnable = VK_FALSE, + .depthBiasConstantFactor = 0.0f, + .depthBiasClamp = 0.0f, + .depthBiasSlopeFactor = 0.0f, + .lineWidth = 1.0f, + }; + + VkPipelineMultisampleStateCreateInfo multisample_info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, + .sampleShadingEnable = VK_FALSE, + .minSampleShading = 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + + VkPipelineColorBlendAttachmentState color_blend_attachment { + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = + VK_COLOR_COMPONENT_R_BIT | + VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | + VK_COLOR_COMPONENT_A_BIT, + }; + + VkPipelineColorBlendStateCreateInfo color_blend_info { + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_COPY, + .attachmentCount = 1, + .pAttachments = &color_blend_attachment, + .blendConstants = { 0.0f, 0.0f, 0.0f, 0.0f }, + }; + + VkPipelineLayoutCreateInfo layout_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + .setLayoutCount = 0, + .pSetLayouts = nullptr, + .pushConstantRangeCount = 0, + .pPushConstantRanges = nullptr, + }; + + if(vkCreatePipelineLayout(m_device, &layout_info, nullptr, &m_pipeline_layout) != VK_SUCCESS) + throw std::runtime_error("failed to create pipeline layout"); + + VkGraphicsPipelineCreateInfo pipeline_info { + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .stageCount = 2, + .pStages = shader_stage_infos, + .pVertexInputState = &vertex_info, + .pInputAssemblyState = &assembly_info, + .pViewportState = &viewport_info, + .pRasterizationState = &rasterization_info, + .pMultisampleState = &multisample_info, + .pDepthStencilState = nullptr, + .pColorBlendState = &color_blend_info, + .pDynamicState = nullptr, + .layout = m_pipeline_layout, + .renderPass = m_render_pass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = -1, + }; + + if(vkCreateGraphicsPipelines(m_device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &m_pipeline) != VK_SUCCESS) + throw std::runtime_error("failed to create graphic pipeline"); +} + +void VulkanTutorial::create_framebuffers() { + m_framebuffers.resize(m_swapchain_image_views.size()); + + for(size_t index = 0; index < m_framebuffers.size(); index += 1) { + VkFramebufferCreateInfo framebuffer_info { + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = m_render_pass, + .attachmentCount = 1, + .pAttachments = &m_swapchain_image_views[index], + .width = m_swapchain_extent.width, + .height = m_swapchain_extent.height, + .layers = 1, + }; + + if(vkCreateFramebuffer(m_device, &framebuffer_info, nullptr, &m_framebuffers[index]) != VK_SUCCESS) + throw std::runtime_error("failed to create framebuffer"); + } +} + +void VulkanTutorial::create_command_pool() { + VkCommandPoolCreateInfo pool_info { + .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, + .flags = 0, + .queueFamilyIndex = m_graphic_queue_family, + }; + + if(vkCreateCommandPool(m_device, &pool_info, nullptr, &m_command_pool) != VK_SUCCESS) + throw std::runtime_error("failed to create command pool"); +} + +void VulkanTutorial::create_command_buffers() { + m_command_buffers.resize(m_framebuffers.size()); + + VkCommandBufferAllocateInfo allocate_info { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .commandPool = m_command_pool, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandBufferCount = uint32_t(m_command_buffers.size()), + }; + + if(vkAllocateCommandBuffers(m_device, &allocate_info, m_command_buffers.data()) != VK_SUCCESS) + throw std::runtime_error("failed to allocate command buffers"); + + for(size_t index = 0; index < m_command_buffers.size(); index += 1) { + VkCommandBuffer command_buffer = m_command_buffers[index]; + + VkCommandBufferBeginInfo begin_info { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = 0, + .pInheritanceInfo = nullptr, + }; + + logger.debug() << "record command buffer " << index << " (" << command_buffer << ")"; + + if(vkBeginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) + throw std::runtime_error("failed to begin command buffer"); + + VkClearValue clear_color = { + .color = { + .float32 = { 0.0f, 0.0f, 0.0f, 1.0f } + } + }; + + VkRenderPassBeginInfo pass_info { + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = m_render_pass, + .framebuffer = m_framebuffers[index], + .renderArea = { + .offset = { 0, 0 }, + .extent = m_swapchain_extent, + }, + .clearValueCount = 1, + .pClearValues = &clear_color, + }; + + vkCmdBeginRenderPass(command_buffer, &pass_info, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline); + vkCmdDraw(command_buffer, 3, 1, 0, 0); + vkCmdEndRenderPass(command_buffer); + + if(vkEndCommandBuffer(command_buffer) != VK_SUCCESS) + throw("failed to record command buffer"); + } +} + +void VulkanTutorial::create_sync_objects() { + m_image_available_semaphores.resize(MAX_FRAMES_IN_FLIGHT); + m_render_finished_semaphores.resize(MAX_FRAMES_IN_FLIGHT); + m_in_flight_fence.resize(MAX_FRAMES_IN_FLIGHT); + m_images_in_flight.resize(m_swapchain_images.size(), VK_NULL_HANDLE); + + VkSemaphoreCreateInfo semaphore_info { + .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, + }; + + VkFenceCreateInfo fence_info { + .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, + .flags = VK_FENCE_CREATE_SIGNALED_BIT, + }; + + for(size_t index = 0; index < MAX_FRAMES_IN_FLIGHT; index += 1) { + if(vkCreateSemaphore(m_device, &semaphore_info, nullptr, &m_image_available_semaphores[index]) != VK_SUCCESS) + throw std::runtime_error("failed to create semaphore"); + if(vkCreateSemaphore(m_device, &semaphore_info, nullptr, &m_render_finished_semaphores[index]) != VK_SUCCESS) + throw std::runtime_error("failed to create semaphore"); + if(vkCreateFence(m_device, &fence_info, nullptr, &m_in_flight_fence[index]) != VK_SUCCESS) + throw std::runtime_error("failed to create fence"); + } +} + +void VulkanTutorial::cleanup_swap_chain() { + if (m_command_pool != VK_NULL_HANDLE && m_command_buffers.size() != 0) + vkFreeCommandBuffers(m_device, m_command_pool, uint32_t(m_command_buffers.size()), m_command_buffers.data()); + m_command_buffers.clear(); + + for(VkFramebuffer framebuffer: m_framebuffers) + vkDestroyFramebuffer(m_device, framebuffer, nullptr); + m_framebuffers.clear(); + + if(m_pipeline != VK_NULL_HANDLE) { + vkDestroyPipeline(m_device, m_pipeline, nullptr); + m_pipeline = VK_NULL_HANDLE; + } + + if(m_pipeline_layout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(m_device, m_pipeline_layout, nullptr); + m_pipeline_layout = VK_NULL_HANDLE; + } + + if(m_render_pass != VK_NULL_HANDLE) { + vkDestroyRenderPass(m_device, m_render_pass, nullptr); + m_render_pass = VK_NULL_HANDLE; + } + + for(VkImageView view: m_swapchain_image_views) + vkDestroyImageView(m_device, view, nullptr); + m_swapchain_image_views.clear(); + + if(m_swapchain) { + vkDestroySwapchainKHR(m_device, m_swapchain, nullptr); + m_swapchain_images.clear(); + m_swapchain = nullptr; + } +} + +void VulkanTutorial::recreate_swap_chain() { + vkDeviceWaitIdle(m_device); + + logger.info() << "recreate swapchain"; + + cleanup_swap_chain(); + + create_swapchain(); + create_render_pass(); + create_graphic_pipeline(); + create_framebuffers(); + create_command_buffers(); +} + +VkShaderModule VulkanTutorial::create_shader_module(const std::vector bytecode) { + VkShaderModuleCreateInfo shader_info { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = bytecode.size(), + .pCode = reinterpret_cast(bytecode.data()), + }; + + VkShaderModule shader_module = VK_NULL_HANDLE; + if(vkCreateShaderModule(m_device, &shader_info, nullptr, &shader_module) != VK_SUCCESS) + throw std::runtime_error("failed to create shader module"); + + return shader_module; +} + +VkShaderModule VulkanTutorial::create_shader_module_from_file(const char* path) { + auto const bytecode = read_binary_file(path); + try { + return create_shader_module(bytecode); + } + catch(std::exception err) { + throw std::runtime_error(cat("failed to create shader '", path, "':\n ", err.what())); + } + return VK_NULL_HANDLE; +} + +std::vector VulkanTutorial::get_physical_devices() const { + uint32_t devices_count = 0; + vkEnumeratePhysicalDevices(m_instance, &devices_count, nullptr); + + std::vector physical_devices(devices_count); + vkEnumeratePhysicalDevices(m_instance, &devices_count, physical_devices.data()); + + return physical_devices; +} + +std::vector VulkanTutorial::get_queue_families(VkPhysicalDevice physical_device) const { + uint32_t queue_families_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, nullptr); + + std::vector queue_families(queue_families_count); + vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, queue_families.data()); + + return queue_families; +} + +std::vector VulkanTutorial::get_device_extensions(VkPhysicalDevice physical_device) const { + uint32_t extensions_count = 0; + vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, nullptr); + + std::vector extensions(extensions_count); + vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, extensions.data()); + + return extensions; +} + +std::vector VulkanTutorial::get_surface_formats(VkPhysicalDevice physical_device) const { + uint32_t surface_formats_count = 0; + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, nullptr); + + std::vector surface_formats(surface_formats_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, surface_formats.data()); + + return surface_formats; +} + +std::vector VulkanTutorial::get_present_modes(VkPhysicalDevice physical_device) const { + uint32_t present_modes_count = 0; + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, nullptr); + + std::vector present_modes(present_modes_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, present_modes.data()); + + return present_modes; +} + +void VulkanTutorial::initialize_extension_functions() { + uint32_t errors_count = 0; + + #define GET_PROC_ADDR(func_name) \ + vke ## func_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \ + m_instance, "vk" #func_name); \ + if(vke ## func_name == nullptr) \ + { \ + logger.error() << "failed to load extension function 'vk" #func_name "'"; \ + errors_count += 1; \ + } + + GET_PROC_ADDR(CreateDebugUtilsMessengerEXT) + GET_PROC_ADDR(DestroyDebugUtilsMessengerEXT) + + if(errors_count != 0) + throw std::runtime_error("failed to load extensions"); +} diff --git a/src/VulkanTutorial.h b/src/VulkanTutorial.h new file mode 100644 index 0000000..8b269c8 --- /dev/null +++ b/src/VulkanTutorial.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include + + +#if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG) +#define VKEXPE_VALIDATION true +#else +#define VKEXPE_VALIDATION false +#endif + +extern PFN_vkCreateDebugUtilsMessengerEXT vkeCreateDebugUtilsMessengerEXT; +extern PFN_vkDestroyDebugUtilsMessengerEXT vkeDestroyDebugUtilsMessengerEXT; + + +class VulkanTutorial { +public: + VulkanTutorial(); + VulkanTutorial(const VulkanTutorial&) = delete; + ~VulkanTutorial(); + + VulkanTutorial& operator=(const VulkanTutorial&) = delete; + + void initialize(SDL_Window* window); + void shutdown(); + + void draw_frame(); + void invalidate_swapchain(); + + static std::vector available_extensions(); + static std::vector available_layers(); + + static VKAPI_ATTR VkBool32 VKAPI_CALL print_debug_message( + VkDebugUtilsMessageSeverityFlagBitsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkDebugUtilsMessengerCallbackDataEXT* data, + void* user_data + ); + +private: + void create_instance(); + void create_surface(); + void find_physical_device(); + void create_device(); + void create_swapchain(); + void create_render_pass(); + void create_graphic_pipeline(); + void create_framebuffers(); + void create_command_pool(); + void create_command_buffers(); + void create_sync_objects(); + + void cleanup_swap_chain(); + void recreate_swap_chain(); + + VkShaderModule create_shader_module(const std::vector bytecode); + VkShaderModule create_shader_module_from_file(const char* path); + + std::vector get_physical_devices() const; + std::vector get_queue_families(VkPhysicalDevice physical_device) const; + std::vector get_device_extensions(VkPhysicalDevice physical_device) const; + std::vector get_surface_formats(VkPhysicalDevice physical_device) const; + std::vector get_present_modes(VkPhysicalDevice physical_device) const; + + void initialize_extension_functions(); + +private: + SDL_Window* m_window = nullptr; + + VkInstance m_instance = nullptr; + VkDebugUtilsMessengerEXT m_debug_messenger = VK_NULL_HANDLE; + VkSurfaceKHR m_surface; + VkPhysicalDevice m_physical_device = nullptr; + VkDevice m_device = nullptr; + VkQueue m_graphic_queue = nullptr; + VkQueue m_presentation_queue = nullptr; + VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; + std::vector m_swapchain_images; + std::vector m_swapchain_image_views; + VkRenderPass m_render_pass = VK_NULL_HANDLE; + VkPipelineLayout m_pipeline_layout = VK_NULL_HANDLE; + VkPipeline m_pipeline = VK_NULL_HANDLE; + std::vector m_framebuffers; + VkCommandPool m_command_pool = VK_NULL_HANDLE; + std::vector m_command_buffers; + std::vector m_image_available_semaphores; + std::vector m_render_finished_semaphores; + std::vector m_in_flight_fence; + std::vector m_images_in_flight; + + uint32_t m_graphic_queue_family = -1; + uint32_t m_presentation_queue_family = -1; + VkSurfaceFormatKHR m_swapchain_format; + VkPresentModeKHR m_present_mode; + VkExtent2D m_swapchain_extent; + + size_t m_frame_index = 0; + bool m_invalid_swapchain = true; + + VkDebugUtilsMessengerCreateInfoEXT m_debug_info; + bool m_enableValidation = VKEXPE_VALIDATION; + + static constexpr size_t MAX_FRAMES_IN_FLIGHT = 2; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..ea76744 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,19 @@ +#include "Logger.h" +#include "VkExpe.h" + +#include + + +int main(int argc, char** argv) { + VkExpe vk_expe(argc, argv); + + try { + vk_expe.run(); + } + catch(std::runtime_error err) { + logger.error() << "Error: " << err.what(); + return 1; + } + + return 0; +} diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..1f50ecd --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,18 @@ +#include "utils.h" + +#include + + +std::vector read_binary_file(const char* path) { + std::ifstream istream(path, std::ios_base::ate | std::ios_base::binary); + if(!istream.is_open()) + throw std::runtime_error(cat("failed to open '", path, "'")); + + std::vector buffer(size_t(istream.tellg())); + + istream.seekg(0); + istream.read(buffer.data(), buffer.size()); + istream.close(); + + return buffer; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..0141bdb --- /dev/null +++ b/src/utils.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + + +template +struct Guard { + Guard(T&& teardown) + : m_teardown(std::move(teardown)) + {} + + ~Guard() { + m_teardown(); + } + +private: + T m_teardown; +}; + +template +Guard make_guard(T&& teardown) { + return Guard(std::move(teardown)); +} + +template +std::string cat(Args&&... args) { + std::ostringstream ostream; + (ostream << ... << args); + return ostream.str(); +} + +std::vector read_binary_file(const char* path);