diff --git a/.gitignore b/.gitignore index 0d767dd..a0a00be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ .vs -build/* out \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d0d55c3..257a05e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ project(VulkanEngine) find_package(Vulkan REQUIRED) +include(cmake/Shaders.cmake) include(FetchContent) FetchContent_Declare( @@ -53,4 +54,12 @@ target_link_libraries(VulkanEngine PRIVATE spdlog) target_include_directories(VulkanEngine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") target_compile_features(VulkanEngine PRIVATE cxx_std_20) -target_precompile_headers(VulkanEngine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/precomp.h") \ No newline at end of file +target_precompile_headers(VulkanEngine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/precomp.h") + +file(GLOB_RECURSE ShaderSources CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.vert" + "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.frag" +) + +add_shaders(VulkanEngineShaders ${ShaderSources}) +add_dependencies(VulkanEngine VulkanEngineShaders) \ No newline at end of file diff --git a/cmake/Shaders.cmake b/cmake/Shaders.cmake new file mode 100644 index 0000000..c5650b2 --- /dev/null +++ b/cmake/Shaders.cmake @@ -0,0 +1,31 @@ +function(add_shaders TARGET_NAME) + set(SHADER_SOURCE_FILES ${ARGN}) + list(LENGTH SHADER_SOURCE_FILES FILE_COUNT) + if(FILE_COUNT EQUAL 0) + message(FATAL_ERROR "Cannot add shaders target without shader files!") + endif() + + set(SHADER_COMMANDS) + set(SHADER_PRODUCTS) + + foreach(SHADER_SOURCE IN LISTS SHADER_SOURCE_FILES) + cmake_path(ABSOLUTE_PATH SHADER_SOURCE NORMALIZE) + cmake_path(GET SHADER_SOURCE FILENAME SHADER_NAME) + + # COMMANDS + list(APPEND SHADER_COMMANDS COMMAND) + list(APPEND SHADER_COMMANDS Vulkan::glslc) + list(APPEND SHADER_COMMANDS "${SHADER_SOURCE}") + list(APPEND SHADER_COMMANDS "-o") + list(APPEND SHADER_COMMANDS "${CMAKE_CURRENT_BINARY_DIR}/${SHADER_NAME}.spv") + # PRODUCTS + list(APPEND SHADER_PRODUCTS "${CMAKE_CURRENT_BINARY_DIR}/${SHADER_NAME}.spv") + endforeach() + + add_custom_target(${TARGET_NAME} ALL + ${SHADER_COMMANDS} + COMMENT "Compiling shaders..." + SOURCES ${SHADER_SOURCE_FILES} + BYPRODUCTS ${SHADER_PRODUCTS} + ) +endfunction() \ No newline at end of file diff --git a/include/glfw/glfw_window.h b/include/glfw/glfw_window.h index a9cf285..8b9fe41 100644 --- a/include/glfw/glfw_window.h +++ b/include/glfw/glfw_window.h @@ -10,6 +10,7 @@ class Window { ~Window(); glm::ivec2 GetWindowSize() const; + glm::ivec2 GetFramebufferSize() const; bool ShouldClose() const; GLFWwindow* GetHandle() const; diff --git a/include/precomp.h b/include/precomp.h index 03052f7..ab426eb 100644 --- a/include/precomp.h +++ b/include/precomp.h @@ -2,12 +2,13 @@ #include #include #include +#include #include #include #include +#include #include -#include #include -#include "utilities.h" #include "spdlog/spdlog.h" +#include "utilities.h" diff --git a/include/utilities.h b/include/utilities.h index d002903..831c597 100644 --- a/include/utilities.h +++ b/include/utilities.h @@ -1,5 +1,8 @@ #pragma once +#include + namespace veng { bool streq(gsl::czstring left, gsl::czstring right); +std::vector ReadFile(std::filesystem::path shader_path); } diff --git a/include/vulkan/graphics.h b/include/vulkan/graphics.h index ce7ff21..c2bb8a1 100644 --- a/include/vulkan/graphics.h +++ b/include/vulkan/graphics.h @@ -2,6 +2,7 @@ #include #include "glfw/glfw_window.h" +#include "precomp.h" namespace veng { class Graphics final { @@ -9,9 +10,33 @@ class Graphics final { Graphics(gsl::not_null window); ~Graphics(); + private: + struct QueueFamilyIndices { + std::optional graphics_family = std::nullopt; + std::optional presentation_family = std::nullopt; + + bool IsValid() const { + return graphics_family.has_value() && presentation_family.has_value(); + } + }; + + struct SwapChainProperties { + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector present_modes; + + bool IsValid() const { return !formats.empty() && !present_modes.empty(); } + }; + void InitializeVulkan(); void CreateInstance(); void SetupDebugMessenger(); + void PickPhysicalDevice(); + void CreateLogicalDeviceAndQueues(); + void CreateSurface(); + void CreateSwapChain(); + void CreateImageViews(); + void CreateGraphicsPipeline(); std::vector GetRequiredInstanceExtentions(); @@ -22,9 +47,48 @@ class Graphics final { static std::vector GetSupprotedValidationLayers(); static bool AreAllLayersSupported(gsl::span extensions); - VkInstance instance_ = nullptr; + QueueFamilyIndices FindQueueFamilies(VkPhysicalDevice device); + SwapChainProperties GetSwapChainProperties(VkPhysicalDevice device); + bool IsDeviceSuitable(VkPhysicalDevice device); + std::vector GetAvailableDevices(); + bool AreAllDeviceExtensionsSupported(VkPhysicalDevice device); + std::vector GetDeviceAvailableExtensions( + VkPhysicalDevice device); + + VkSurfaceFormatKHR ChooseSwapSurfaceFormat( + gsl::span formats); + VkPresentModeKHR ChooseSwapPresentMode( + gsl::span present_modes); + VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities); + std::uint32_t ChooseSwapImageCount(const VkSurfaceCapabilitiesKHR& capabilities); + + VkShaderModule CreateShaderModule(gsl::span buffer); + + std::array required_device_extentions_ = { + VK_KHR_SWAPCHAIN_EXTENSION_NAME}; + + VkInstance instance_ = VK_NULL_HANDLE; VkDebugUtilsMessengerEXT debug_messenger_; + + VkPhysicalDevice physical_device_ = VK_NULL_HANDLE; + VkDevice logical_device_ = VK_NULL_HANDLE; + VkQueue graphics_queue_ = VK_NULL_HANDLE; + VkQueue present_queue_ = VK_NULL_HANDLE; + + VkSurfaceKHR surface_ = VK_NULL_HANDLE; + VkSwapchainKHR swap_chain_ = VK_NULL_HANDLE; + VkSurfaceFormatKHR surface_format_; + VkPresentModeKHR present_mode_; + VkExtent2D extent_; + std::vector swap_chain_images_; + std::vector swap_chain_image_views_; + gsl::not_null window_; bool validation_enabled_ = false; }; + +VkDebugUtilsMessengerCreateInfoEXT GetCreateMessengerInfo(); +bool IsExtensionSupported(gsl::span extensions, + gsl::czstring name); + } // namespace veng diff --git a/shaders/basic.frag b/shaders/basic.frag new file mode 100644 index 0000000..112d91f --- /dev/null +++ b/shaders/basic.frag @@ -0,0 +1,8 @@ +#version 450 +#include "common.glsl" + +layout(location = 0) out vec4 out_color; + +void main() { + out_color = vec4(1.0, 0.0, 0.5, 1.0); +} \ No newline at end of file diff --git a/shaders/basic.vert b/shaders/basic.vert new file mode 100644 index 0000000..9dc0a35 --- /dev/null +++ b/shaders/basic.vert @@ -0,0 +1,13 @@ +#version 450 +#include "common.glsl" + +vec2 hardcoded_positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +void main() { + vec2 current_Position = hardcoded_positions[gl_VertexIndex]; + gl_Position = vec4(current_Position, 0.0, 1.0); +} \ No newline at end of file diff --git a/shaders/common.glsl b/shaders/common.glsl new file mode 100644 index 0000000..eb86877 --- /dev/null +++ b/shaders/common.glsl @@ -0,0 +1 @@ +#extension GL_KHR_vulkan_glsl : enable \ No newline at end of file diff --git a/src/glfw/glfw_window.cpp b/src/glfw/glfw_window.cpp index 24747ed..e401b94 100644 --- a/src/glfw/glfw_window.cpp +++ b/src/glfw/glfw_window.cpp @@ -22,6 +22,12 @@ glm::ivec2 Window::GetWindowSize() const { return window_size; } +glm::ivec2 Window::GetFramebufferSize() const { + glm::ivec2 size; + glfwGetFramebufferSize(window_, &size.x, &size.y); + return size; +} + bool Window::ShouldClose() const { return glfwWindowShouldClose(window_); } GLFWwindow* Window::GetHandle() const { return window_; } diff --git a/src/utilities.cpp b/src/utilities.cpp index 6991d68..1f8410f 100644 --- a/src/utilities.cpp +++ b/src/utilities.cpp @@ -1,9 +1,28 @@ #include "utilities.h" +#include + #include "precomp.h" namespace veng { bool streq(gsl::czstring left, gsl::czstring right) { return std::strcmp(left, right) == 0; } +std::vector ReadFile(std::filesystem::path shader_path) { + if (!std::filesystem::exists(shader_path)) return {}; + if (!std::filesystem::is_regular_file(shader_path)) return {}; + + std::ifstream file(shader_path, std::ios::binary); + + if (!file.is_open()) return {}; + + std::uintmax_t file_size = std::filesystem::file_size(shader_path); + if (file_size > std::numeric_limits::max()) return {}; // 방어 + + std::uint32_t size = static_cast(file_size); + std::vector buffer(size); + file.read(reinterpret_cast(buffer.data()), size); + + return buffer; +} } // namespace veng diff --git a/src/vulkan/devices_and_queues.cpp b/src/vulkan/devices_and_queues.cpp new file mode 100644 index 0000000..e11a176 --- /dev/null +++ b/src/vulkan/devices_and_queues.cpp @@ -0,0 +1,164 @@ +#include + +#include "precomp.h" +#include "vulkan/graphics.h" + +namespace veng { + +Graphics::QueueFamilyIndices Graphics::FindQueueFamilies( + VkPhysicalDevice device) { + std::uint32_t queue_familiy_count = 0; + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_familiy_count, + nullptr); + std::vector families(queue_familiy_count); + vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_familiy_count, + families.data()); + + auto graphics_family_it = + std::find_if(families.begin(), families.end(), + [](const VkQueueFamilyProperties& props) { + return props.queueFlags & + (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_TRANSFER_BIT); + }); + + QueueFamilyIndices result; + result.graphics_family = graphics_family_it - families.begin(); + + for (std::uint32_t i = 0; i < families.size(); i++) { + VkBool32 has_presentation_support = false; + vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface_, + &has_presentation_support); + if (has_presentation_support) { + result.presentation_family = i; + break; + } + } + + return result; +} + +Graphics::SwapChainProperties Graphics::GetSwapChainProperties( + VkPhysicalDevice device) { + SwapChainProperties properties; + + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface_, + &properties.capabilities); + + std::uint32_t format_count; + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &format_count, + nullptr); + properties.formats.resize(format_count); + vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface_, &format_count, + properties.formats.data()); + + std::uint32_t modes_count; + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &modes_count, + nullptr); + properties.present_modes.resize(modes_count); + vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface_, &modes_count, + properties.present_modes.data()); + + return properties; +} + +std::vector Graphics::GetDeviceAvailableExtensions( + VkPhysicalDevice device) { + std::uint32_t available_extentions_count; + vkEnumerateDeviceExtensionProperties(device, nullptr, + &available_extentions_count, nullptr); + std::vector available_extentions( + available_extentions_count); + vkEnumerateDeviceExtensionProperties(device, nullptr, + &available_extentions_count, + available_extentions.data()); + return available_extentions; +} + +bool Graphics::AreAllDeviceExtensionsSupported(VkPhysicalDevice device) { + std::vector available_extentions = + GetDeviceAvailableExtensions(device); + return std::all_of( + required_device_extentions_.begin(), required_device_extentions_.end(), + std::bind_front(IsExtensionSupported, available_extentions)); +} + +bool Graphics::IsDeviceSuitable(VkPhysicalDevice device) { + QueueFamilyIndices families = FindQueueFamilies(device); + return families.IsValid() && AreAllDeviceExtensionsSupported(device) && + GetSwapChainProperties(device).IsValid(); +} + +void Graphics::PickPhysicalDevice() { + std::vector devices = GetAvailableDevices(); + + std::erase_if( + devices, std::not_fn(std::bind_front(&Graphics::IsDeviceSuitable, this))); + + if (devices.empty()) { + spdlog::error("No physical devices that match the criteria"); + std::exit(EXIT_FAILURE); + } + + physical_device_ = devices[0]; +} + +std::vector Graphics::GetAvailableDevices() { + std::uint32_t device_count; + vkEnumeratePhysicalDevices(instance_, &device_count, nullptr); + + if (device_count == 0) return {}; + + std::vector devices(device_count); + vkEnumeratePhysicalDevices(instance_, &device_count, devices.data()); + + return devices; +} + +void Graphics::CreateLogicalDeviceAndQueues() { + QueueFamilyIndices picked_device_families = + FindQueueFamilies(physical_device_); + + if (!picked_device_families.IsValid()) { + std::exit(EXIT_FAILURE); + } + + std::set unique_queue_families = { + picked_device_families.graphics_family.value(), + picked_device_families.presentation_family.value(), + }; + + std::float_t queue_priority = 1.f; + + std::vector queue_create_infos; + for (std::uint32_t unique_queue_family : unique_queue_families) { + VkDeviceQueueCreateInfo queue_info = {}; + queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_info.queueFamilyIndex = unique_queue_family; + queue_info.queueCount = 1; + queue_info.pQueuePriorities = &queue_priority; + queue_create_infos.push_back(queue_info); + } + + VkPhysicalDeviceFeatures required_features = {}; + + VkDeviceCreateInfo device_info = {}; + device_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_info.queueCreateInfoCount = queue_create_infos.size(); + device_info.pQueueCreateInfos = queue_create_infos.data(); + device_info.pEnabledFeatures = &required_features; + device_info.enabledExtensionCount = required_device_extentions_.size(); + device_info.ppEnabledExtensionNames = required_device_extentions_.data(); + + VkResult result = + vkCreateDevice(physical_device_, &device_info, nullptr, &logical_device_); + if (result != VK_SUCCESS) std::exit(EXIT_FAILURE); + + vkGetDeviceQueue(logical_device_, + picked_device_families.graphics_family.value(), 0, + &graphics_queue_); + vkGetDeviceQueue(logical_device_, + picked_device_families.presentation_family.value(), 0, + &present_queue_); +} + +} // namespace veng diff --git a/src/vulkan/graphics.cpp b/src/vulkan/graphics.cpp index 6271848..6f86e8b 100644 --- a/src/vulkan/graphics.cpp +++ b/src/vulkan/graphics.cpp @@ -6,58 +6,8 @@ #include "precomp.h" -VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT( - VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, - const VkAllocationCallbacks* pAllocator, - VkDebugUtilsMessengerEXT* pMessenger) { - PFN_vkCreateDebugUtilsMessengerEXT function = - reinterpret_cast( - vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); - if (function != nullptr) { - return function(instance, pCreateInfo, pAllocator, pMessenger); - } else { - return VK_ERROR_EXTENSION_NOT_PRESENT; - } -} - -VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT( - VkInstance instance, VkDebugUtilsMessengerEXT messenger, - const VkAllocationCallbacks* pAllocator) { - PFN_vkDestroyDebugUtilsMessengerEXT function = - reinterpret_cast( - vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); - if (function != nullptr) function(instance, messenger, pAllocator); -} - namespace veng { -static VKAPI_ATTR VkBool32 VKAPI_CALL -ValidationCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - VkDebugUtilsMessageTypeFlagsEXT messageTypes, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void* pUserData) { - if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { - spdlog::warn("Vulkan Validation: {}", pCallbackData->pMessage); - } else { - spdlog::error("Vulkan Validation: {}", pCallbackData->pMessage); - } - return VK_FALSE; -} - -static VkDebugUtilsMessengerCreateInfoEXT GetCreateMessengerInfo() { - VkDebugUtilsMessengerCreateInfoEXT creation_info = {}; - creation_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; - creation_info.messageSeverity = - VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; - creation_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | - VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; - creation_info.pfnUserCallback = ValidationCallback; - creation_info.pUserData = nullptr; - - return creation_info; -} - Graphics::Graphics(gsl::not_null window) : window_(window) { #if !defined(NDEBUG) validation_enabled_ = true; @@ -66,10 +16,18 @@ Graphics::Graphics(gsl::not_null window) : window_(window) { } Graphics::~Graphics() { - if (instance_ != nullptr) { - if (debug_messenger_ != nullptr) { + if (logical_device_ != VK_NULL_HANDLE) { + for (VkImageView image_view : swap_chain_image_views_) + vkDestroyImageView(logical_device_, image_view, nullptr); + if (swap_chain_ != VK_NULL_HANDLE) + vkDestroySwapchainKHR(logical_device_, swap_chain_, nullptr); + vkDestroyDevice(logical_device_, nullptr); + } + if (instance_ != VK_NULL_HANDLE) { + if (surface_ != VK_NULL_HANDLE) + vkDestroySurfaceKHR(instance_, surface_, nullptr); + if (debug_messenger_ != VK_NULL_HANDLE) vkDestroyDebugUtilsMessengerEXT(instance_, debug_messenger_, nullptr); - } vkDestroyInstance(instance_, nullptr); } } @@ -77,140 +35,11 @@ Graphics::~Graphics() { void Graphics::InitializeVulkan() { CreateInstance(); SetupDebugMessenger(); -} - -void Graphics::CreateInstance() { - std::array validation_layers = { - "VK_LAYER_KHRONOS_validation"}; - - if (!AreAllLayersSupported(validation_layers)) validation_enabled_ = false; - - std::vector required_extentions = - GetRequiredInstanceExtentions(); - - VkApplicationInfo app_info = {}; - app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; - app_info.pNext = nullptr; - app_info.pApplicationName = "Udemy Course"; - app_info.applicationVersion = VK_MAKE_API_VERSION(0, 0, 0, 0); - app_info.pEngineName = "VEng"; - app_info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); - app_info.apiVersion = VK_API_VERSION_1_0; - - VkInstanceCreateInfo instance_creation_info = {}; - instance_creation_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; - instance_creation_info.pNext = nullptr; - instance_creation_info.pApplicationInfo = &app_info; - instance_creation_info.enabledExtensionCount = required_extentions.size(); - instance_creation_info.ppEnabledExtensionNames = required_extentions.data(); - - VkDebugUtilsMessengerCreateInfoEXT messenger_create_info = - GetCreateMessengerInfo(); - if (validation_enabled_) { - instance_creation_info.pNext = &messenger_create_info; - instance_creation_info.enabledLayerCount = validation_layers.size(); - instance_creation_info.ppEnabledLayerNames = validation_layers.data(); - } else { - instance_creation_info.enabledLayerCount = 0; - instance_creation_info.ppEnabledLayerNames = nullptr; - } - - VkResult result = - vkCreateInstance(&instance_creation_info, nullptr, &instance_); - if (result != VK_SUCCESS) std::exit(EXIT_FAILURE); -} - -void Graphics::SetupDebugMessenger() { - if (!validation_enabled_) return; - - VkDebugUtilsMessengerCreateInfoEXT info = GetCreateMessengerInfo(); - VkResult result = vkCreateDebugUtilsMessengerEXT(instance_, &info, nullptr, - &debug_messenger_); - if (result != VK_SUCCESS) { - spdlog::error("Cannot create debug messenger"); - return; - } -} - -gsl::span Graphics::GetSuggestedInstanceExtentions() { - std::uint32_t glfw_extention_count = 0; - gsl::czstring* glfw_extentions = - glfwGetRequiredInstanceExtensions(&glfw_extention_count); - return {glfw_extentions, glfw_extention_count}; -} - -std::vector Graphics::GetRequiredInstanceExtentions() { - gsl::span suggested_extentions = - GetSuggestedInstanceExtentions(); - std::vector required_extentions(suggested_extentions.size()); - std::copy(suggested_extentions.begin(), suggested_extentions.end(), - required_extentions.begin()); - - if (validation_enabled_) - required_extentions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); - - if (!AreAllExtensionsSupported(suggested_extentions)) std::exit(EXIT_FAILURE); - - return required_extentions; -} - -std::vector Graphics::GetSupprotedInstanceExtensions() { - std::uint32_t count; - vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); - - if (count == 0) return {}; - - std::vector properties(count); - vkEnumerateInstanceExtensionProperties(nullptr, &count, properties.data()); - return properties; -} - -bool ExtentionMatchesName(gsl::czstring name, - const VkExtensionProperties& properties) { - return streq(properties.extensionName, name); -} - -bool IsExtensionSupported(gsl::span extensions, - gsl::czstring name) { - return std::any_of(extensions.begin(), extensions.end(), - std::bind_front(ExtentionMatchesName, name)); -} - -bool Graphics::AreAllExtensionsSupported(gsl::span extensions) { - std::vector supported_extensions = - GetSupprotedInstanceExtensions(); - - return std::all_of( - extensions.begin(), extensions.end(), - std::bind_front(IsExtensionSupported, supported_extensions)); -} - -std::vector Graphics::GetSupprotedValidationLayers() { - std::uint32_t count; - vkEnumerateInstanceLayerProperties(&count, nullptr); - - if (count == 0) return {}; - - std::vector properties(count); - vkEnumerateInstanceLayerProperties(&count, properties.data()); - return properties; -} - -bool LayerMatchesName(gsl::czstring name, const VkLayerProperties& properties) { - return streq(properties.layerName, name); -} - -bool IsLayerSupported(gsl::span layers, gsl::czstring name) { - return std::any_of(layers.begin(), layers.end(), - std::bind_front(LayerMatchesName, name)); -} - -bool Graphics::AreAllLayersSupported(gsl::span layers) { - std::vector supported_layers = - GetSupprotedValidationLayers(); - - return std::all_of(layers.begin(), layers.end(), - std::bind_front(IsLayerSupported, supported_layers)); + CreateSurface(); + PickPhysicalDevice(); + CreateLogicalDeviceAndQueues(); + CreateSwapChain(); + CreateGraphicsPipeline(); } } // namespace veng diff --git a/src/vulkan/graphics_pipeline.cpp b/src/vulkan/graphics_pipeline.cpp new file mode 100644 index 0000000..312a3b2 --- /dev/null +++ b/src/vulkan/graphics_pipeline.cpp @@ -0,0 +1,56 @@ +#include + +#include "precomp.h" +#include "vulkan/graphics.h" + +namespace veng { + +VkShaderModule Graphics::CreateShaderModule(gsl::span buffer) { + if (buffer.empty()) return VK_NULL_HANDLE; + + VkShaderModuleCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + info.codeSize = buffer.size(); + info.pCode = reinterpret_cast(buffer.data()); + + VkShaderModule shader_module; + VkResult result = + vkCreateShaderModule(logical_device_, &info, nullptr, &shader_module); + if (result != VK_SUCCESS) return VK_NULL_HANDLE; + + return shader_module; +} + +void Graphics::CreateGraphicsPipeline() { + std::vector basic_vertex_data = ReadFile("./basic.vert.spv"); + VkShaderModule vertex_shader = CreateShaderModule(basic_vertex_data); + gsl::final_action _destroy_vertex([this, vertex_shader]() { + vkDestroyShaderModule(logical_device_, vertex_shader, nullptr); + }); + + std::vector basic_fragment_data = ReadFile("./basic.frag.spv"); + VkShaderModule fragment_shader = CreateShaderModule(basic_fragment_data); + gsl::final_action _destroy_fragment([this, fragment_shader]() { + vkDestroyShaderModule(logical_device_, fragment_shader, nullptr); + }); + + if (vertex_shader == VK_NULL_HANDLE || fragment_shader == VK_NULL_HANDLE) + std::exit(EXIT_FAILURE); + + VkPipelineShaderStageCreateInfo vertex_stage_info = {}; + vertex_stage_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + vertex_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT; + vertex_stage_info.module = vertex_shader; + vertex_stage_info.pName = "main"; + + VkPipelineShaderStageCreateInfo fragment_stage_info = {}; + fragment_stage_info.sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + fragment_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT; + fragment_stage_info.module = fragment_shader; + fragment_stage_info.pName = "main"; + + std::array stage_infos = { + vertex_stage_info, fragment_stage_info}; +} +} // namespace veng diff --git a/src/vulkan/instance_and_extensions.cpp b/src/vulkan/instance_and_extensions.cpp new file mode 100644 index 0000000..5806859 --- /dev/null +++ b/src/vulkan/instance_and_extensions.cpp @@ -0,0 +1,102 @@ +#include + +#include "precomp.h" +#include "vulkan/graphics.h" + +namespace veng { + +gsl::span Graphics::GetSuggestedInstanceExtentions() { + std::uint32_t glfw_extention_count = 0; + gsl::czstring* glfw_extentions = + glfwGetRequiredInstanceExtensions(&glfw_extention_count); + return {glfw_extentions, glfw_extention_count}; +} + +std::vector Graphics::GetRequiredInstanceExtentions() { + gsl::span suggested_extentions = + GetSuggestedInstanceExtentions(); + std::vector required_extentions(suggested_extentions.size()); + std::copy(suggested_extentions.begin(), suggested_extentions.end(), + required_extentions.begin()); + + if (validation_enabled_) + required_extentions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + + if (!AreAllExtensionsSupported(suggested_extentions)) std::exit(EXIT_FAILURE); + + return required_extentions; +} + +std::vector Graphics::GetSupprotedInstanceExtensions() { + std::uint32_t count; + vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr); + + if (count == 0) return {}; + + std::vector properties(count); + vkEnumerateInstanceExtensionProperties(nullptr, &count, properties.data()); + return properties; +} + +bool ExtentionMatchesName(gsl::czstring name, + const VkExtensionProperties& properties) { + return streq(properties.extensionName, name); +} + +bool IsExtensionSupported(gsl::span extensions, + gsl::czstring name) { + return std::any_of(extensions.begin(), extensions.end(), + std::bind_front(ExtentionMatchesName, name)); +} + +bool Graphics::AreAllExtensionsSupported(gsl::span extensions) { + std::vector supported_extensions = + GetSupprotedInstanceExtensions(); + + return std::all_of( + extensions.begin(), extensions.end(), + std::bind_front(IsExtensionSupported, supported_extensions)); +} + +void Graphics::CreateInstance() { + std::array validation_layers = { + "VK_LAYER_KHRONOS_validation"}; + + if (!AreAllLayersSupported(validation_layers)) validation_enabled_ = false; + + std::vector required_extentions = + GetRequiredInstanceExtentions(); + + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pNext = nullptr; + app_info.pApplicationName = "Udemy Course"; + app_info.applicationVersion = VK_MAKE_API_VERSION(0, 0, 0, 0); + app_info.pEngineName = "VEng"; + app_info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); + app_info.apiVersion = VK_API_VERSION_1_0; + + VkInstanceCreateInfo instance_creation_info = {}; + instance_creation_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_creation_info.pNext = nullptr; + instance_creation_info.pApplicationInfo = &app_info; + instance_creation_info.enabledExtensionCount = required_extentions.size(); + instance_creation_info.ppEnabledExtensionNames = required_extentions.data(); + + VkDebugUtilsMessengerCreateInfoEXT messenger_create_info = + GetCreateMessengerInfo(); + if (validation_enabled_) { + instance_creation_info.pNext = &messenger_create_info; + instance_creation_info.enabledLayerCount = validation_layers.size(); + instance_creation_info.ppEnabledLayerNames = validation_layers.data(); + } else { + instance_creation_info.enabledLayerCount = 0; + instance_creation_info.ppEnabledLayerNames = nullptr; + } + + VkResult result = + vkCreateInstance(&instance_creation_info, nullptr, &instance_); + if (result != VK_SUCCESS) std::exit(EXIT_FAILURE); +} + +} // namespace veng diff --git a/src/vulkan/presentation.cpp b/src/vulkan/presentation.cpp new file mode 100644 index 0000000..60a837e --- /dev/null +++ b/src/vulkan/presentation.cpp @@ -0,0 +1,170 @@ +#include "vulkan/graphics.h" +#include + +#include "precomp.h" + +namespace veng { + +void Graphics::CreateSurface() { + VkResult result = glfwCreateWindowSurface(instance_, window_->GetHandle(), + nullptr, &surface_); + if (result != VK_SUCCESS) std::exit(EXIT_FAILURE); +} + +bool IsRgbaTypeFormat(const VkSurfaceFormatKHR& format_properties) { + return format_properties.format == VK_FORMAT_R8G8B8A8_SRGB || + format_properties.format == VK_FORMAT_B8G8R8A8_SRGB; +} + +bool IsSrgbColorSpace(const VkSurfaceFormatKHR& format_properties) { + return format_properties.colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR; +} + +bool IsCorrectFormat(const VkSurfaceFormatKHR& format_properties) { + return IsSrgbColorSpace(format_properties) && + IsRgbaTypeFormat(format_properties); +} + +VkSurfaceFormatKHR Graphics::ChooseSwapSurfaceFormat( + gsl::span formats) { + if (formats.size() == 1 && formats[0].format == VK_FORMAT_UNDEFINED) { + return {VkFormat::VK_FORMAT_R8G8B8A8_SRGB, + VkColorSpaceKHR::VK_COLORSPACE_SRGB_NONLINEAR_KHR}; + } + + auto it = std::find_if(formats.begin(), formats.end(), IsCorrectFormat); + if (it != formats.end()) return *it; + + for (const VkSurfaceFormatKHR& format : formats) { + if (format.format == VK_FORMAT_R8G8B8A8_SRGB && + format.colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) { + return format; + } + } + + return formats[0]; +} + +VkPresentModeKHR Graphics::ChooseSwapPresentMode( + gsl::span present_modes) { + constexpr std::array preferred_modes = { + VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_KHR}; + + for (const auto& preferred : preferred_modes) + if (std::find(present_modes.begin(), present_modes.end(), preferred) != + present_modes.end()) + return preferred; + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D Graphics::ChooseSwapExtent( + const VkSurfaceCapabilitiesKHR& capabilities) { + constexpr std::uint32_t kInvalidSize = + std::numeric_limits::max(); + + if (capabilities.currentExtent.width != kInvalidSize) { + return capabilities.currentExtent; + } else { + glm::ivec2 size = window_->GetFramebufferSize(); + VkExtent2D actual_extent = {static_cast(size.x), + static_cast(size.y)}; + + actual_extent.width = + std::clamp(actual_extent.width, capabilities.minImageExtent.width, + capabilities.maxImageExtent.width); + actual_extent.height = + std::clamp(actual_extent.height, capabilities.minImageExtent.height, + capabilities.maxImageExtent.height); + + return actual_extent; + } +} + +std::uint32_t Graphics::ChooseSwapImageCount( + const VkSurfaceCapabilitiesKHR& capabilities) { + std::uint32_t image_count = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && + capabilities.maxImageCount < image_count) + image_count = capabilities.maxImageCount; + + return image_count; +} + +void Graphics::CreateSwapChain() { + SwapChainProperties properties = GetSwapChainProperties(physical_device_); + + surface_format_ = ChooseSwapSurfaceFormat(properties.formats); + present_mode_ = ChooseSwapPresentMode(properties.present_modes); + extent_ = ChooseSwapExtent(properties.capabilities); + + std::uint32_t image_count = ChooseSwapImageCount(properties.capabilities); + + VkSwapchainCreateInfoKHR info = {}; + info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + info.surface = surface_; + info.minImageCount = image_count; + info.imageFormat = surface_format_.format; + info.imageColorSpace = surface_format_.colorSpace; + info.imageExtent = extent_; + info.imageArrayLayers = 1; + info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + info.presentMode = present_mode_; + info.preTransform = properties.capabilities.currentTransform; + info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + info.clipped = VK_TRUE; + info.oldSwapchain = VK_NULL_HANDLE; + + QueueFamilyIndices indices = FindQueueFamilies(physical_device_); + + if (indices.graphics_family != indices.presentation_family) { + std::array family_indices = { + indices.graphics_family.value(), indices.presentation_family.value()}; + info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + info.queueFamilyIndexCount = family_indices.size(); + info.pQueueFamilyIndices = family_indices.data(); + } else { + info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + VkResult result = + vkCreateSwapchainKHR(logical_device_, &info, nullptr, &swap_chain_); + if (result != VK_SUCCESS) std::exit(EXIT_FAILURE); + + std::uint32_t actual_image_count; + vkGetSwapchainImagesKHR(logical_device_, swap_chain_, &actual_image_count, + nullptr); + swap_chain_images_.resize(actual_image_count); + vkGetSwapchainImagesKHR(logical_device_, swap_chain_, &actual_image_count, + swap_chain_images_.data()); +} + +void Graphics::CreateImageViews() { + swap_chain_image_views_.resize(swap_chain_images_.size()); + + auto image_view_it = swap_chain_image_views_.begin(); + for (VkImage image : swap_chain_images_) { + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = surface_format_.format; + info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + info.subresourceRange.baseMipLevel = 0; + info.subresourceRange.layerCount = 1; + info.subresourceRange.baseArrayLayer = 0; + info.subresourceRange.layerCount = 1; + + VkResult result = + vkCreateImageView(logical_device_, &info, nullptr, &*image_view_it); + if (result != VK_SUCCESS) std::exit(EXIT_FAILURE); + std::next(image_view_it); + } +} + +} // namespace veng diff --git a/src/vulkan/validation_layers.cpp b/src/vulkan/validation_layers.cpp new file mode 100644 index 0000000..b37c94e --- /dev/null +++ b/src/vulkan/validation_layers.cpp @@ -0,0 +1,73 @@ +#include "precomp.h" +#include "vulkan/graphics.h" + +namespace veng { + +static VKAPI_ATTR VkBool32 VKAPI_CALL +ValidationCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageTypes, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) { + if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { + spdlog::warn("Vulkan Validation: {}", pCallbackData->pMessage); + } else { + spdlog::error("Vulkan Validation: {}", pCallbackData->pMessage); + } + return VK_FALSE; +} + +VkDebugUtilsMessengerCreateInfoEXT GetCreateMessengerInfo() { + VkDebugUtilsMessengerCreateInfoEXT creation_info = {}; + creation_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + creation_info.messageSeverity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + creation_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; + creation_info.pfnUserCallback = ValidationCallback; + creation_info.pUserData = nullptr; + + return creation_info; +} + +std::vector Graphics::GetSupprotedValidationLayers() { + std::uint32_t count; + vkEnumerateInstanceLayerProperties(&count, nullptr); + + if (count == 0) return {}; + + std::vector properties(count); + vkEnumerateInstanceLayerProperties(&count, properties.data()); + return properties; +} + +bool LayerMatchesName(gsl::czstring name, const VkLayerProperties& properties) { + return streq(properties.layerName, name); +} + +bool IsLayerSupported(gsl::span layers, gsl::czstring name) { + return std::any_of(layers.begin(), layers.end(), + std::bind_front(LayerMatchesName, name)); +} + +bool Graphics::AreAllLayersSupported(gsl::span layers) { + std::vector supported_layers = + GetSupprotedValidationLayers(); + + return std::all_of(layers.begin(), layers.end(), + std::bind_front(IsLayerSupported, supported_layers)); +} + +void Graphics::SetupDebugMessenger() { + if (!validation_enabled_) return; + + VkDebugUtilsMessengerCreateInfoEXT info = GetCreateMessengerInfo(); + VkResult result = vkCreateDebugUtilsMessengerEXT(instance_, &info, nullptr, + &debug_messenger_); + if (result != VK_SUCCESS) { + spdlog::error("Cannot create debug messenger"); + return; + } +} + +} // namespace veng diff --git a/src/vulkan/vk_function_ext_impl.cpp b/src/vulkan/vk_function_ext_impl.cpp new file mode 100644 index 0000000..d4517a5 --- /dev/null +++ b/src/vulkan/vk_function_ext_impl.cpp @@ -0,0 +1,25 @@ +#include "precomp.h" +#include "vulkan/graphics.h" + +VKAPI_ATTR VkResult VKAPI_CALL vkCreateDebugUtilsMessengerEXT( + VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pMessenger) { + PFN_vkCreateDebugUtilsMessengerEXT function = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); + if (function != nullptr) { + return function(instance, pCreateInfo, pAllocator, pMessenger); + } else { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +VKAPI_ATTR void VKAPI_CALL vkDestroyDebugUtilsMessengerEXT( + VkInstance instance, VkDebugUtilsMessengerEXT messenger, + const VkAllocationCallbacks* pAllocator) { + PFN_vkDestroyDebugUtilsMessengerEXT function = + reinterpret_cast( + vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); + if (function != nullptr) function(instance, messenger, pAllocator); +}