From d9ca075c86218811fceb0806884cc41374243236 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Thu, 30 Jan 2020 01:15:10 -0700 Subject: [PATCH] First commit of Vk-Bootstrap Library contains barebones implementations for simplified Instance creation Physical Device selection Device creation Swapchain Creation/Recreation Much of the repo is WIP, and there is little to no testing. Also, while a single header file is desired for the future, currently it is structured more like a normal project with .cpp files --- .clang-format | 50 +++++ .gitignore | 2 + CMakeLists.txt | 29 +++ README.md | 49 +++++ src/Device.cpp | 513 ++++++++++++++++++++++++++++++++++++++++++++ src/Device.h | 180 ++++++++++++++++ src/Instance.cpp | 349 ++++++++++++++++++++++++++++++ src/Instance.h | 113 ++++++++++ src/Swapchain.cpp | 154 +++++++++++++ src/Swapchain.h | 58 +++++ src/Util.h | 170 +++++++++++++++ src/VkBootstrap.h | 5 + tests/run_tests.cpp | 181 ++++++++++++++++ 13 files changed, 1853 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 src/Device.cpp create mode 100644 src/Device.h create mode 100644 src/Instance.cpp create mode 100644 src/Instance.h create mode 100644 src/Swapchain.cpp create mode 100644 src/Swapchain.h create mode 100644 src/Util.h create mode 100644 src/VkBootstrap.h create mode 100644 tests/run_tests.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f932ab2 --- /dev/null +++ b/.clang-format @@ -0,0 +1,50 @@ +BasedOnStyle: LLVM + +AccessModifierOffset: 0 +AlignEscapedNewlinesLeft: false +AlignTrailingComments: true +AlignAfterOpenBracket: DontAlign +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortFunctionsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: false +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: false +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +CommentPragmas: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 0 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerBinding: false +IndentCaseLabels: true +IndentFunctionDeclarationAfterType: false +IndentWidth: 4 +Language: Cpp +MaxEmptyLinesToKeep: 4 +NamespaceIndentation: None +PenaltyBreakBeforeFirstCallParameter: 100 +PenaltyBreakComment: 100 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 100 +PenaltyExcessCharacter: 1 +PenaltyReturnTypeOnItsOwnLine: 20 +PointerBindsToType: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Always +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +Standard: Auto +TabWidth: 4 +UseTab: ForIndentation +SortIncludes: false \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..493424b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.vscode +*.vs \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e3beb5e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) +project(VulkanBootstrap) + +find_package(Vulkan REQUIRED) + +find_package(glfw3) + +add_library(vk-bootstrap + src/VkBootstrap.h + src/Instance.cpp + src/Device.cpp + src/Swapchain.cpp) + +target_include_directories(vk-bootstrap PUBLIC src) + +target_include_directories(vk-bootstrap PRIVATE ${Vulkan_INCLUDE_DIRS}) +target_link_libraries(vk-bootstrap PRIVATE ${Vulkan_LIBRARIES}) + +target_compile_features(vk-bootstrap PUBLIC cxx_std_11) + +target_compile_options(vk-bootstrap PUBLIC -fsanitize=address) +target_link_options(vk-bootstrap PUBLIC -fsanitize=address) + + + +add_executable(vk-bootstrap-test tests/run_tests.cpp) + +target_link_libraries(vk-bootstrap-test vk-bootstrap) +target_link_libraries(vk-bootstrap-test glfw) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..44a7823 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# Vk-Bootstrap + +A Vulkan Utility library meant to jump start a Vulkan Application + +This library simplifies the tedious process of: + +* Instance Creation +* Physical Device Selection +* Device Creation +* Getting Queues +* Swapchain Creation + +## Example + +```cpp +vkbs::InstanceBuilder builder; +builder.setup_validation_layers() + .set_app_name ("example") + .set_default_debug_messenger (); +auto inst_ret = builder.build (); +vkbs::Instance inst; +if (inst_ret.has_value()) { + // successfully created instance + inst = inst_ret.value(); +} + +vkbs::PhysicalDeviceSelector; +selector.set_instance(inst) + .set_surface (/* from user created window*/) + .set_minimum_version (1, 0) + .require_dedicated_transfer_queue(); +auto phys_ret = selector.select (); +vkbs::PhysicalDevice phys; +if (phys_ret.has_value()) { + // successfully selected a sufficient physical device + phys = phys_ret.value(); +} + +vkbs::DeviceBuilder device_builder; +device_builder.set_physical_device (phys_dev); +auto dev_ret = device_builder.build (); +if(dev_ret.has_value()){ + // successfully created a vulkan device +} + +``` + +## Building + diff --git a/src/Device.cpp b/src/Device.cpp new file mode 100644 index 0000000..9db396f --- /dev/null +++ b/src/Device.cpp @@ -0,0 +1,513 @@ +#include "Device.h" + +#include + +namespace vkbs +{ + +namespace detail +{ +Expected query_surface_support_details ( + VkPhysicalDevice phys_device, VkSurfaceKHR surface) +{ + if (surface == VK_NULL_HANDLE) + return Error{ VK_ERROR_INITIALIZATION_FAILED, "surface handle was null" }; + + VkSurfaceCapabilitiesKHR capabilities; + VkResult res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR (phys_device, surface, &capabilities); + if (res != VK_SUCCESS) + { + // error + /* possible errors + VK_ERROR_OUT_OF_HOST_MEMORY + VK_ERROR_OUT_OF_DEVICE_MEMORY + VK_ERROR_SURFACE_LOST_KHR + */ + } + auto formats = detail::get_vector ( + vkGetPhysicalDeviceSurfaceFormatsKHR, phys_device, surface); + if (!formats.has_value ()) + return detail::Error{ formats.error ().error_code, "Couldn't get surface formats" }; + auto present_modes = detail::get_vector ( + vkGetPhysicalDeviceSurfacePresentModesKHR, phys_device, surface); + if (!present_modes.has_value ()) + return detail::Error{ formats.error ().error_code, "Couldn't get surface present modes" }; + + return SurfaceSupportDetails{ capabilities, formats.value (), present_modes.value () }; +} + + +// Given a list of formats, return a format supported by the hardware, else return VK_FORMAT_UNDEFINED +VkFormat find_supported_format ( + VkPhysicalDevice physical_device, const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features) +{ + for (VkFormat format : candidates) + { + VkFormatProperties props; + vkGetPhysicalDeviceFormatProperties (physical_device, format, &props); + + if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) + { + return format; + } + else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) + { + return format; + } + } + return VK_FORMAT_UNDEFINED; +} + +bool check_device_extension_support (VkPhysicalDevice device, std::vector extensions) +{ + auto available_extensions = + detail::get_vector (vkEnumerateDeviceExtensionProperties, device, nullptr); + if (!available_extensions.has_value ()) return false; // maybe handle error + + bool all_available = true; + for (const auto& extension : available_extensions.value ()) + { + bool found = false; + for (auto& req_ext : extensions) + { + if (req_ext == extension.extensionName) found = true; + break; + } + if (!found) all_available = false; + } + return all_available; +} + +detail::QueueFamilies find_queue_families (VkPhysicalDevice phys_device, VkSurfaceKHR surface) +{ + auto queue_families = detail::get_vector_noerror ( + vkGetPhysicalDeviceQueueFamilyProperties, phys_device); + + QueueFamilies families; + int dedicated_compute = -1; + int dedicated_transfer = -1; + + for (int i = 0; i < queue_families.size (); i++) + { + auto& queueFlags = queue_families[i].queueFlags; + if (queueFlags & VK_QUEUE_GRAPHICS_BIT) families.graphics = i; + if (queueFlags & VK_QUEUE_COMPUTE_BIT) families.compute = i; + if (queueFlags & VK_QUEUE_TRANSFER_BIT) families.transfer = i; + if (queueFlags & VK_QUEUE_SPARSE_BINDING_BIT) families.sparse = i; + + // compute that isn't graphics + if (queueFlags & VK_QUEUE_COMPUTE_BIT && ((queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) + dedicated_compute = i; + + // transfer that isn't computer or graphics + if (queueFlags & VK_QUEUE_TRANSFER_BIT && ((queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) && + ((queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) + dedicated_transfer = i; + + VkBool32 presentSupport = false; + if (surface != VK_NULL_HANDLE) + { + VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR (phys_device, i, surface, &presentSupport); + } + if (presentSupport == true) families.present = i; + } + + if (dedicated_compute != -1) families.compute = dedicated_compute; + if (dedicated_transfer != -1) families.transfer = dedicated_transfer; + + // compute and transfer always supported on the graphics family + if (families.compute != -1 && queue_families[families.graphics].queueFlags & VK_QUEUE_COMPUTE_BIT) + families.compute = families.graphics; + if (families.transfer != -1 && queue_families[families.graphics].queueFlags & VK_QUEUE_TRANSFER_BIT) + families.transfer = families.graphics; + + families.count_graphics = queue_families[families.graphics].queueCount; + families.count_transfer = queue_families[families.transfer].queueCount; + families.count_compute = queue_families[families.compute].queueCount; + if (families.sparse != -1) families.count_sparse = queue_families[families.sparse].queueCount; + return families; +} + +bool supports_features (VkPhysicalDeviceFeatures supported, VkPhysicalDeviceFeatures requested) +{ + // clang-format off + if (requested.robustBufferAccess && ! supported.robustBufferAccess) return false; + if (requested.fullDrawIndexUint32 && ! supported.fullDrawIndexUint32) return false; + if (requested.imageCubeArray && ! supported.imageCubeArray) return false; + if (requested.independentBlend && ! supported.independentBlend) return false; + if (requested.geometryShader && ! supported.geometryShader) return false; + if (requested.tessellationShader && ! supported.tessellationShader) return false; + if (requested.sampleRateShading && ! supported.sampleRateShading) return false; + if (requested.dualSrcBlend && ! supported.dualSrcBlend) return false; + if (requested.logicOp && ! supported.logicOp) return false; + if (requested.multiDrawIndirect && ! supported.multiDrawIndirect) return false; + if (requested.drawIndirectFirstInstance && ! supported.drawIndirectFirstInstance) return false; + if (requested.depthClamp && ! supported.depthClamp) return false; + if (requested.depthBiasClamp && ! supported.depthBiasClamp) return false; + if (requested.fillModeNonSolid && ! supported.fillModeNonSolid) return false; + if (requested.depthBounds && ! supported.depthBounds) return false; + if (requested.wideLines && ! supported.wideLines) return false; + if (requested.largePoints && ! supported.largePoints) return false; + if (requested.alphaToOne && ! supported.alphaToOne) return false; + if (requested.multiViewport && ! supported.multiViewport) return false; + if (requested.samplerAnisotropy && ! supported.samplerAnisotropy) return false; + if (requested.textureCompressionETC2 && ! supported.textureCompressionETC2) return false; + if (requested.textureCompressionASTC_LDR && ! supported.textureCompressionASTC_LDR) return false; + if (requested.textureCompressionBC && ! supported.textureCompressionBC) return false; + if (requested.occlusionQueryPrecise && ! supported.occlusionQueryPrecise) return false; + if (requested.pipelineStatisticsQuery && ! supported.pipelineStatisticsQuery) return false; + if (requested.vertexPipelineStoresAndAtomics && !supported.vertexPipelineStoresAndAtomics) return false; + if (requested.fragmentStoresAndAtomics && !supported.fragmentStoresAndAtomics) return false; + if (requested.shaderTessellationAndGeometryPointSize && !supported.shaderTessellationAndGeometryPointSize) return false; + if (requested.shaderImageGatherExtended && !supported.shaderImageGatherExtended) return false; + if (requested.shaderStorageImageExtendedFormats && !supported.shaderStorageImageExtendedFormats) return false; + if (requested.shaderStorageImageMultisample && !supported.shaderStorageImageMultisample) return false; + if (requested.shaderStorageImageReadWithoutFormat && !supported.shaderStorageImageReadWithoutFormat) return false; + if (requested.shaderStorageImageWriteWithoutFormat && !supported.shaderStorageImageWriteWithoutFormat) return false; + if (requested.shaderUniformBufferArrayDynamicIndexing && !supported.shaderUniformBufferArrayDynamicIndexing) return false; + if (requested.shaderSampledImageArrayDynamicIndexing && !supported.shaderSampledImageArrayDynamicIndexing) return false; + if (requested.shaderStorageBufferArrayDynamicIndexing && !supported.shaderStorageBufferArrayDynamicIndexing) return false; + if (requested.shaderStorageImageArrayDynamicIndexing && !supported.shaderStorageImageArrayDynamicIndexing) return false; + if (requested.shaderClipDistance && !supported.shaderClipDistance) return false; + if (requested.shaderCullDistance && !supported.shaderCullDistance) return false; + if (requested.shaderFloat64 && !supported.shaderFloat64) return false; + if (requested.shaderInt64 && !supported.shaderInt64) return false; + if (requested.shaderInt16 && !supported.shaderInt16) return false; + if (requested.shaderResourceResidency && !supported.shaderResourceResidency) return false; + if (requested.shaderResourceMinLod && !supported.shaderResourceMinLod) return false; + if (requested.sparseBinding && !supported.sparseBinding) return false; + if (requested.sparseResidencyBuffer && !supported.sparseResidencyBuffer) return false; + if (requested.sparseResidencyImage2D && !supported.sparseResidencyImage2D) return false; + if (requested.sparseResidencyImage3D && !supported.sparseResidencyImage3D) return false; + if (requested.sparseResidency2Samples && !supported.sparseResidency2Samples) return false; + if (requested.sparseResidency4Samples && !supported.sparseResidency4Samples) return false; + if (requested.sparseResidency8Samples && !supported.sparseResidency8Samples) return false; + if (requested.sparseResidency16Samples && !supported.sparseResidency16Samples) return false; + if (requested.sparseResidencyAliased && !supported.sparseResidencyAliased) return false; + if (requested.variableMultisampleRate && !supported.variableMultisampleRate) return false; + if (requested.inheritedQueries && !supported.inheritedQueries) return false; + // clang-format on + return true; +} + +void populate_physical_device_details (PhysicalDevice phys_device) +{ + vkGetPhysicalDeviceFeatures (phys_device.phys_device, &phys_device.physical_device_features); + vkGetPhysicalDeviceProperties (phys_device.phys_device, &phys_device.physical_device_properties); + vkGetPhysicalDeviceMemoryProperties (phys_device.phys_device, &phys_device.memory_properties); +} + +} // namespace detail + + +PhysicalDeviceSelector::Suitable PhysicalDeviceSelector::is_device_suitable (VkPhysicalDevice phys_device) +{ + Suitable suitable = Suitable::yes; + + detail::QueueFamilies indices = detail::find_queue_families (phys_device, info.surface); + + if (criteria.require_dedicated_compute_queue && indices.graphics != indices.compute) + suitable = Suitable::no; + if (criteria.require_dedicated_transfer_queue && indices.graphics != indices.transfer) + suitable = Suitable::no; + if (criteria.require_present && indices.present == -1) suitable = Suitable::no; + + bool required_extensions_supported = + detail::check_device_extension_support (phys_device, criteria.required_extensions); + if (!required_extensions_supported) suitable = Suitable::no; + + bool desired_extensions_supported = + detail::check_device_extension_support (phys_device, criteria.desired_extensions); + if (!desired_extensions_supported) suitable = Suitable::partial; + + + bool swapChainAdequate = false; + if (!info.headless) + { + auto swapChainSupport_ret = detail::query_surface_support_details (phys_device, info.surface); + if (swapChainSupport_ret.has_value ()) + { + auto swapchain_support = swapChainSupport_ret.value (); + swapChainAdequate = + !swapchain_support.formats.empty () && !swapchain_support.present_modes.empty (); + } + } + if (criteria.require_present && !swapChainAdequate) suitable = Suitable::no; + + VkPhysicalDeviceMemoryProperties mem_properties; + vkGetPhysicalDeviceMemoryProperties (phys_device, &mem_properties); + + bool has_required_memory = false; + bool has_preferred_memory = false; + for (int i = 0; i < mem_properties.memoryHeapCount; i++) + { + if (mem_properties.memoryHeaps[i].flags & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) + { + if (mem_properties.memoryHeaps[i].size > criteria.required_mem_size) + { + has_required_memory = true; + } + if (mem_properties.memoryHeaps[i].size > criteria.desired_mem_size) + { + has_preferred_memory = true; + } + } + } + if (!has_required_memory) suitable = Suitable::no; + if (!has_preferred_memory) suitable = Suitable::partial; + + VkPhysicalDeviceProperties device_properties; + vkGetPhysicalDeviceProperties (phys_device, &device_properties); + if ((criteria.prefer_discrete && device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) || + (criteria.prefer_integrated && device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)) + { + if (criteria.allow_fallback) + suitable = Suitable::partial; + else + suitable = Suitable::no; + } + if (criteria.required_version < device_properties.apiVersion) suitable = Suitable::no; + if (criteria.desired_version < device_properties.apiVersion) suitable = Suitable::partial; + + VkPhysicalDeviceFeatures supported_features{}; + vkGetPhysicalDeviceFeatures (phys_device, &supported_features); + bool all_features_supported = detail::supports_features (supported_features, criteria.required_features); + + return suitable; +} + +detail::Expected PhysicalDeviceSelector::select () +{ + auto physical_devices = detail::get_vector (vkEnumeratePhysicalDevices, info.instance); + if (!physical_devices.has_value ()) + { + return detail::Error{ physical_devices.error ().error_code, "Failed to find physical devices" }; + } + + PhysicalDevice physical_device; + for (const auto& device : physical_devices.value ()) + { + auto suitable = is_device_suitable (device); + if (suitable == Suitable::yes) + { + physical_device.phys_device = device; + break; + } + else if (suitable == Suitable::partial) + { + physical_device.phys_device = device; + } + } + + if (physical_device.phys_device == VK_NULL_HANDLE) + { + return detail::Error{ VK_ERROR_INITIALIZATION_FAILED, "Failed to find a suitable GPU!" }; + } + detail::populate_physical_device_details (physical_device); + physical_device.physical_device_features = criteria.required_features; + + physical_device.queue_family_properties = + detail::find_queue_families (physical_device.phys_device, info.surface); + return physical_device; +} + +PhysicalDeviceSelector& PhysicalDeviceSelector::set_instance (Instance instance) +{ + info.instance = instance.instance; + info.headless = instance.headless; + criteria.require_present = !instance.headless; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::set_surface (VkSurfaceKHR surface) +{ + info.surface = surface; + info.headless = false; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::prefer_discrete (bool prefer_discrete) +{ + criteria.prefer_discrete = prefer_discrete; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::prefer_integrated (bool prefer_integrated) +{ + criteria.prefer_integrated = prefer_integrated; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::allow_fallback (bool fallback) +{ + criteria.allow_fallback = fallback; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::require_present (bool require) +{ + criteria.require_present = require; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::require_dedicated_transfer_queue () +{ + criteria.require_dedicated_transfer_queue = true; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::require_dedicated_compute_queue () +{ + criteria.require_dedicated_compute_queue = true; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::required_device_memory_size (VkDeviceSize size) +{ + criteria.required_mem_size = size; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::desired_device_memory_size (VkDeviceSize size) +{ + criteria.desired_mem_size = size; + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::add_required_extension (std::string extension) +{ + criteria.required_extensions.push_back (extension); + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::add_desired_extension (std::string extension) +{ + criteria.desired_extensions.push_back (extension); + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::set_minimum_version (uint32_t major, uint32_t minor) +{ + criteria.required_version = VK_MAKE_VERSION (major, minor, 0); + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::set_desired_version (uint32_t major, uint32_t minor) +{ + criteria.desired_version = VK_MAKE_VERSION (major, minor, 0); + return *this; +} +PhysicalDeviceSelector& PhysicalDeviceSelector::set_required_features (VkPhysicalDeviceFeatures features) +{ + criteria.required_features = features; + return *this; +} + +// ---- Device ---- // + +void destroy_device (Device device) { vkDestroyDevice (device.device, device.allocator); } + +struct QueueFamily +{ + int32_t family; + uint32_t count; +}; +detail::Expected DeviceBuilder::build () +{ + auto& queue_properties = info.physical_device.queue_family_properties; + std::vector families; + families.push_back ({ queue_properties.graphics, queue_properties.count_graphics }); + if (queue_properties.compute != -1 && queue_properties.compute != queue_properties.graphics) + families.push_back ({ queue_properties.compute, queue_properties.count_compute }); + if (queue_properties.transfer != -1 && queue_properties.transfer != queue_properties.graphics) + families.push_back ({ queue_properties.transfer, queue_properties.count_transfer }); + if (queue_properties.sparse != -1) + families.push_back ({ queue_properties.sparse, queue_properties.count_sparse }); + + std::vector queueCreateInfos; + float queuePriority = 1.0f; + for (auto& queue : families) + { + VkDeviceQueueCreateInfo queue_create_info = {}; + queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_info.queueFamilyIndex = static_cast (queue.family); + queue_create_info.queueCount = queue.count; + queue_create_info.pQueuePriorities = &queuePriority; + queueCreateInfos.push_back (queue_create_info); + } + + std::vector extensions; + for (auto& ext : info.extensions) + extensions.push_back (ext.c_str ()); + if (info.physical_device.surface != VK_NULL_HANDLE) + extensions.push_back ({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }); + + VkDeviceCreateInfo device_create_info = {}; + device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + device_create_info.pNext = info.pNext_chain; + device_create_info.flags = info.flags; + device_create_info.queueCreateInfoCount = static_cast (queueCreateInfos.size ()); + device_create_info.pQueueCreateInfos = queueCreateInfos.data (); + device_create_info.enabledExtensionCount = static_cast (extensions.size ()); + device_create_info.ppEnabledExtensionNames = extensions.data (); + device_create_info.pEnabledFeatures = &info.physical_device.physical_device_features; + + Device device; + VkResult res = + vkCreateDevice (info.physical_device.phys_device, &device_create_info, nullptr, &device.device); + if (res != VK_SUCCESS) + { + return detail::Error{ res, "Couldn't create device" }; + } + return device; +} + +DeviceBuilder& DeviceBuilder::set_physical_device (PhysicalDevice const& phys_device) +{ + info.physical_device = phys_device; + return *this; +} + +template DeviceBuilder& DeviceBuilder::add_pNext (T* structure) +{ + if (info.pNext_chain == nullptr) + info.pNext_chain = (VkBaseOutStructure*)structure; + else + detail::pNext_append (info.pNext_chain, structure); + return *this; +} + +// ---- Queue ---- // + +namespace detail +{ +VkQueue get_queue (Device const& device, uint32_t family, uint32_t index) +{ + VkQueue queue; + vkGetDeviceQueue (device.device, family, index, &queue); + return queue; +} +} // namespace detail +detail::Expected get_queue_present (Device const& device) +{ + return detail::get_queue (device, device.physical_device.queue_family_properties.present, 0); +} +detail::Expected get_queue_graphics (Device const& device, uint32_t index) +{ + if (index >= device.physical_device.queue_family_properties.count_graphics) + return detail::Error{ VK_ERROR_INITIALIZATION_FAILED, + "requested graphics queue index is out of bounds" }; + return detail::get_queue (device, device.physical_device.queue_family_properties.graphics, index); +} +detail::Expected get_queue_compute (Device const& device, uint32_t index) +{ + if (index >= device.physical_device.queue_family_properties.count_compute) + return detail::Error{ VK_ERROR_INITIALIZATION_FAILED, + "requested compute queue index is out of bounds" }; + return detail::get_queue (device, device.physical_device.queue_family_properties.compute, index); +} +detail::Expected get_queue_transfer (Device const& device, uint32_t index) +{ + if (index >= device.physical_device.queue_family_properties.count_transfer) + return detail::Error{ VK_ERROR_INITIALIZATION_FAILED, + "requested transfer queue index is out of bounds" }; + return detail::get_queue (device, device.physical_device.queue_family_properties.transfer, index); +} + +detail::Expected get_queue_sparse (Device const& device, uint32_t index) +{ + if (index >= device.physical_device.queue_family_properties.count_sparse) + return detail::Error{ VK_ERROR_INITIALIZATION_FAILED, + "requested sparse queue index is out of bounds" }; + return detail::get_queue (device, device.physical_device.queue_family_properties.sparse, index); +} + + +} // namespace vkbs \ No newline at end of file diff --git a/src/Device.h b/src/Device.h new file mode 100644 index 0000000..109b563 --- /dev/null +++ b/src/Device.h @@ -0,0 +1,180 @@ +#pragma once + +#include "Util.h" + +#include "Instance.h" + +namespace vkbs +{ +namespace detail +{ + +struct SurfaceSupportDetails +{ + VkSurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector present_modes; +}; + +Expected query_surface_support_details ( + VkPhysicalDevice phys_device, VkSurfaceKHR surface); + +struct QueueFamilies +{ + int graphics = -1; + int present = -1; + int transfer = -1; + int compute = -1; + int sparse = -1; + uint32_t count_graphics = 0; + uint32_t count_transfer = 0; + uint32_t count_compute = 0; + uint32_t count_sparse = 0; +}; + +VkFormat find_supported_format (VkPhysicalDevice physical_device, + const std::vector& candidates, + VkImageTiling tiling, + VkFormatFeatureFlags features); + +bool check_device_extension_support (VkPhysicalDevice device, std::vector extensions); + +detail::QueueFamilies find_queue_families (VkPhysicalDevice physDevice, VkSurfaceKHR windowSurface); + +bool supports_features (VkPhysicalDeviceFeatures supported, VkPhysicalDeviceFeatures requested); + +} // namespace detail + +// ---- Physical Device ---- // + +struct PhysicalDevice +{ + VkPhysicalDevice phys_device = VK_NULL_HANDLE; + VkSurfaceKHR surface = VK_NULL_HANDLE; + + VkPhysicalDeviceProperties physical_device_properties{}; + VkPhysicalDeviceFeatures physical_device_features{}; + VkPhysicalDeviceMemoryProperties memory_properties{}; + + detail::QueueFamilies queue_family_properties; +}; + +namespace detail +{ +void populate_physical_device_details (PhysicalDevice physical_device); +} // namespace detail + +struct PhysicalDeviceSelector +{ + public: + detail::Expected select (); + + PhysicalDeviceSelector& set_instance (Instance instance); + PhysicalDeviceSelector& set_surface (VkSurfaceKHR instance); + + PhysicalDeviceSelector& prefer_discrete (bool prefer_discrete = true); + PhysicalDeviceSelector& prefer_integrated (bool prefer_integrated = true); + PhysicalDeviceSelector& allow_fallback (bool fallback = true); + + PhysicalDeviceSelector& require_present (bool require = true); + PhysicalDeviceSelector& require_dedicated_transfer_queue (); + PhysicalDeviceSelector& require_dedicated_compute_queue (); + + PhysicalDeviceSelector& required_device_memory_size (VkDeviceSize size); + PhysicalDeviceSelector& desired_device_memory_size (VkDeviceSize size); + + PhysicalDeviceSelector& add_required_extension (std::string extension); + PhysicalDeviceSelector& add_desired_extension (std::string extension); + + PhysicalDeviceSelector& set_desired_version (uint32_t major, uint32_t minor); + PhysicalDeviceSelector& set_minimum_version (uint32_t major = 1, uint32_t minor = 0); + + PhysicalDeviceSelector& set_required_features (VkPhysicalDeviceFeatures features); + + private: + struct PhysicalDeviceInfo + { + VkInstance instance = VK_NULL_HANDLE; + VkSurfaceKHR surface = VK_NULL_HANDLE; + bool headless = false; + } info; + + struct SelectionCriteria + { + bool prefer_discrete = true; + bool prefer_integrated = false; + bool allow_fallback = true; + bool require_present = true; + bool require_dedicated_transfer_queue = false; + bool require_dedicated_compute_queue = false; + VkDeviceSize required_mem_size = 0; + VkDeviceSize desired_mem_size = 0; + + std::vector required_extensions; + std::vector desired_extensions; + + uint32_t required_version = VK_MAKE_VERSION (1, 0, 0); + uint32_t desired_version = VK_MAKE_VERSION (1, 0, 0); + + VkPhysicalDeviceFeatures required_features{}; + + } criteria; + + enum class Suitable + { + yes, + partial, + no + }; + + Suitable is_device_suitable (VkPhysicalDevice phys_device); +}; + +// ---- Device ---- // + +struct Device +{ + VkDevice device = VK_NULL_HANDLE; + VkAllocationCallbacks* allocator = VK_NULL_HANDLE; + PhysicalDevice physical_device; + VkSurfaceKHR surface = VK_NULL_HANDLE; +}; + +void destroy_device (Device device); + +class DeviceBuilder +{ + public: + detail::Expected build (); + + DeviceBuilder& set_physical_device (PhysicalDevice const& phys_device); + + template DeviceBuilder& add_pNext (T* structure); + + private: + struct DeviceInfo + { + VkDeviceCreateFlags flags; + VkBaseOutStructure* pNext_chain = nullptr; + VkAllocationCallbacks* allocator = VK_NULL_HANDLE; + PhysicalDevice physical_device; + std::vector extensions; + } info; +}; + +// ---- Queue ---- // + +namespace detail +{ +VkQueue get_queue (Device const& device, uint32_t family, uint32_t index = 0); +} + +detail::Expected get_queue_present (Device const& device); +detail::Expected get_queue_graphics (Device const& device, uint32_t index = 0); +detail::Expected get_queue_compute (Device const& device, uint32_t index = 0); +detail::Expected get_queue_transfer (Device const& device, uint32_t index = 0); +detail::Expected get_queue_sparse (Device const& device, uint32_t index = 0); + + + +} // namespace vkbs \ No newline at end of file diff --git a/src/Instance.cpp b/src/Instance.cpp new file mode 100644 index 0000000..7bc5c9b --- /dev/null +++ b/src/Instance.cpp @@ -0,0 +1,349 @@ +#include "Instance.h" + +#include +#include + +namespace vkbs +{ + +const char* DebugMessageSeverity (VkDebugUtilsMessageSeverityFlagBitsEXT s) +{ + switch (s) + { + case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + return "VERBOSE"; + case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: + return "ERROR"; + case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: + return "WARNING"; + case VkDebugUtilsMessageSeverityFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + return "INFO"; + default: + return "UNKNOWN"; + } +} +const char* DebugMessageType (VkDebugUtilsMessageTypeFlagsEXT s) +{ + switch (s) + { + case VkDebugUtilsMessageTypeFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT: + return "General"; + case VkDebugUtilsMessageTypeFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT: + return "Validation"; + case VkDebugUtilsMessageTypeFlagBitsEXT::VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT: + return "Performance"; + default: + return "Unknown"; + } +} + +namespace detail +{ + +VkResult create_debug_utils_messenger (VkInstance instance, + PFN_vkDebugUtilsMessengerCallbackEXT debug_callback, + VkDebugUtilsMessageSeverityFlagsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger) +{ + if (debug_callback == nullptr) debug_callback = default_debug_callback; + VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = {}; + messengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + messengerCreateInfo.messageSeverity = severity; + messengerCreateInfo.messageType = type; + messengerCreateInfo.pfnUserCallback = debug_callback; + + + auto vkCreateDebugUtilsMessengerEXT_func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr ( + instance, "vkCreateDebugUtilsMessengerEXT"); + if (vkCreateDebugUtilsMessengerEXT_func != nullptr) + { + return vkCreateDebugUtilsMessengerEXT_func (instance, &messengerCreateInfo, pAllocator, pDebugMessenger); + } + else + { + return VK_ERROR_EXTENSION_NOT_PRESENT; + } +} + +void destroy_debug_utils_messenger ( + VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) +{ + auto vkDestroyDebugUtilsMessengerEXT_func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr ( + instance, "vkDestroyDebugUtilsMessengerEXT"); + if (vkDestroyDebugUtilsMessengerEXT_func != nullptr) + { + vkDestroyDebugUtilsMessengerEXT_func (instance, debugMessenger, pAllocator); + } +} + +VkBool32 default_debug_callback (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) +{ + auto ms = DebugMessageSeverity (messageSeverity); + auto mt = DebugMessageType (messageType); + printf ("[%s: %s]\n%s\n", ms, mt, pCallbackData->pMessage); + return VK_FALSE; +} + +bool check_layers_supported (std::vector layer_names) +{ + auto available_layers = detail::get_vector (vkEnumerateInstanceLayerProperties); + if (!available_layers.has_value ()) return false; // maybe report error? + bool all_found = true; + for (const auto& layer_name : layer_names) + { + bool found = false; + for (const auto& layer_properties : available_layers.value ()) + { + if (strcmp (layer_name, layer_properties.layerName) == 0) + { + found = true; + break; + } + } + if (!found) all_found = false; + } + + return all_found; +} + +} // namespace detail + +void destroy_instance (Instance instance) +{ + if (instance.debug_messenger != nullptr) + detail::destroy_debug_utils_messenger (instance.instance, instance.debug_messenger, instance.allocator); + if (instance.instance != VK_NULL_HANDLE) + vkDestroyInstance (instance.instance, instance.allocator); +} + +detail::Expected InstanceBuilder::build () +{ + + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.pNext = nullptr; + app_info.pApplicationName = info.app_name.c_str (); + app_info.applicationVersion = info.application_version; + app_info.pEngineName = info.engine_name.c_str (); + app_info.engineVersion = info.engine_version; + app_info.apiVersion = info.api_version; + + std::vector extensions; + for (auto& ext : info.extensions) + extensions.push_back (ext.c_str ()); + if (info.debug_callback != nullptr) + { + extensions.push_back (VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + } + + if (!info.headless_context) + { + extensions.push_back (VK_KHR_SURFACE_EXTENSION_NAME); +#if defined(_WIN32) + extentions.push_back (VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_ANDROID_KHR) + extentions.push_back (VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); +#elif defined(_DIRECT2DISPLAY) + extentions.push_back (VK_KHR_DISPLAY_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_WAYLAND_KHR) + extentions.push_back (VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_XCB_KHR) + extentions.push_back (VK_KHR_XCB_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_X11_HKR) + extentions.push_back (VK_KHR_X11_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_IOS_MVK) + extentions.push_back (VK_MVK_IOS_SURFACE_EXTENSION_NAME); +#elif defined(VK_USE_PLATFORM_MACOS_MVK) + extentions.push_back (VK_MVK_MACOS_SURFACE_EXTENSION_NAME); +#endif + } + + std::vector layers; + for (auto& layer : info.layers) + layers.push_back (layer.c_str ()); + + if (info.enable_validation_layers) + { + layers.push_back ("VK_LAYER_KHRONOS_validation"); + } + bool all_layers_supported = detail::check_layers_supported (layers); + if (!all_layers_supported) + { + return detail::Error{ VK_ERROR_LAYER_NOT_PRESENT, "Not all layers supported!" }; + } + + VkBaseOutStructure pNext_chain; + pNext_chain.pNext = (VkBaseOutStructure*)info.pNext; + if (info.use_debug_messenger) + { + VkDebugUtilsMessengerCreateInfoEXT messengerCreateInfo = {}; + messengerCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + messengerCreateInfo.messageSeverity = info.debug_message_severity; + messengerCreateInfo.messageType = info.debug_message_type; + messengerCreateInfo.pfnUserCallback = info.debug_callback; + detail::pNext_append (&pNext_chain, &messengerCreateInfo); + } + + if (info.enabled_validation_features.size () != 0 || info.disabled_validation_features.size ()) + { + VkValidationFeaturesEXT features{}; + features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT; + features.pNext = nullptr; + features.enabledValidationFeatureCount = info.enabled_validation_features.size (); + features.pEnabledValidationFeatures = info.enabled_validation_features.data (); + features.disabledValidationFeatureCount = info.disabled_validation_features.size (); + features.pDisabledValidationFeatures = info.disabled_validation_features.data (); + detail::pNext_append (&pNext_chain, &features); + } + + if (info.disabled_validation_checks.size () != 0) + { + VkValidationFlagsEXT checks{}; + checks.sType = VK_STRUCTURE_TYPE_VALIDATION_FLAGS_EXT; + checks.pNext = nullptr; + checks.disabledValidationCheckCount = info.disabled_validation_checks.size (); + checks.pDisabledValidationChecks = info.disabled_validation_checks.data (); + detail::pNext_append (&pNext_chain, &checks); + } + + VkInstanceCreateInfo instance_create_info = {}; + instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; + instance_create_info.pNext = pNext_chain.pNext; + instance_create_info.flags = info.flags; + instance_create_info.pApplicationInfo = &app_info; + instance_create_info.enabledExtensionCount = static_cast (extensions.size ()); + instance_create_info.ppEnabledExtensionNames = extensions.data (); + instance_create_info.enabledLayerCount = static_cast (layers.size ()); + instance_create_info.ppEnabledLayerNames = layers.data (); + + Instance instance; + VkResult res = vkCreateInstance (&instance_create_info, info.allocator, &instance.instance); + if (res != VK_SUCCESS) return detail::Error{ res, "Failed to create instance" }; + + res = detail::create_debug_utils_messenger (instance.instance, + info.debug_callback, + info.debug_message_severity, + info.debug_message_type, + info.allocator, + &instance.debug_messenger); + if (res != VK_SUCCESS) + return detail::Error{ res, "Failed to create setup debug callback" }; + + if (info.headless_context) + { + instance.headless = true; + } + return instance; +} + +InstanceBuilder& InstanceBuilder::set_app_name (std::string app_name) +{ + info.app_name = app_name; + return *this; +} +InstanceBuilder& InstanceBuilder::set_engine_name (std::string engine_name) +{ + info.engine_name = engine_name; + return *this; +} + +InstanceBuilder& InstanceBuilder::set_app_version (uint32_t major, uint32_t minor, uint32_t patch) +{ + info.application_version = VK_MAKE_VERSION (major, minor, patch); + return *this; +} +InstanceBuilder& InstanceBuilder::set_engine_version (uint32_t major, uint32_t minor, uint32_t patch) +{ + info.engine_version = VK_MAKE_VERSION (major, minor, patch); + return *this; +} +InstanceBuilder& InstanceBuilder::set_api_version (uint32_t major, uint32_t minor, uint32_t patch) +{ + info.api_version = VK_MAKE_VERSION (major, minor, patch); + return *this; +} + +InstanceBuilder& InstanceBuilder::add_layer (std::string layer_name) +{ + info.layers.push_back (layer_name); + return *this; +} +InstanceBuilder& InstanceBuilder::add_extension (std::string extension_name) +{ + info.extensions.push_back (extension_name); + return *this; +} + +InstanceBuilder& InstanceBuilder::setup_validation_layers (bool enable_validation) +{ + info.enable_validation_layers = enable_validation; + return *this; +} +InstanceBuilder& InstanceBuilder::set_default_debug_messenger () +{ + info.use_debug_messenger = true; + info.debug_callback = detail::default_debug_callback; + return *this; +} + +InstanceBuilder& InstanceBuilder::set_debug_callback (PFN_vkDebugUtilsMessengerCallbackEXT callback) +{ + info.use_debug_messenger = true; + info.debug_callback = callback; + return *this; +} +InstanceBuilder& InstanceBuilder::set_headless (bool headless) +{ + info.headless_context = headless; + return *this; +} + +InstanceBuilder& InstanceBuilder::set_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity) +{ + info.debug_message_severity = severity; + return *this; +} +InstanceBuilder& InstanceBuilder::add_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity) +{ + info.debug_message_severity = info.debug_message_severity | severity; + return *this; +} +InstanceBuilder& InstanceBuilder::set_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type) +{ + info.debug_message_type = type; + return *this; +} +InstanceBuilder& InstanceBuilder::add_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type) +{ + info.debug_message_type = info.debug_message_type | type; + return *this; +} + +InstanceBuilder& InstanceBuilder::add_validation_disable (VkValidationCheckEXT check) +{ + info.disabled_validation_checks.push_back (check); + return *this; +} +InstanceBuilder& InstanceBuilder::add_validation_feature_enable (VkValidationFeatureEnableEXT enable) +{ + info.enabled_validation_features.push_back (enable); + return *this; +} +InstanceBuilder& InstanceBuilder::add_validation_feature_disable (VkValidationFeatureDisableEXT disable) +{ + info.disabled_validation_features.push_back (disable); + return *this; +} + +InstanceBuilder& InstanceBuilder::set_allocator_callback (VkAllocationCallbacks* allocator) +{ + info.allocator = allocator; + return *this; +} + +} // namespace vkbs \ No newline at end of file diff --git a/src/Instance.h b/src/Instance.h new file mode 100644 index 0000000..f1cf4c5 --- /dev/null +++ b/src/Instance.h @@ -0,0 +1,113 @@ +#pragma once + +#include "Util.h" + +namespace vkbs +{ + +const char* DebugMessageSeverity (VkDebugUtilsMessageSeverityFlagBitsEXT s); +const char* DebugMessageType (VkDebugUtilsMessageTypeFlagsEXT s); + +namespace detail +{ +VkResult create_debug_utils_messenger (VkInstance instance, + PFN_vkDebugUtilsMessengerCallbackEXT debug_callback, + VkDebugUtilsMessageSeverityFlagsEXT severity, + VkDebugUtilsMessageTypeFlagsEXT type, + const VkAllocationCallbacks* pAllocator, + VkDebugUtilsMessengerEXT* pDebugMessenger); + +void destroy_debug_utils_messenger ( + VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator); + +static VKAPI_ATTR VkBool32 VKAPI_CALL default_debug_callback (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData); + +bool check_layers_supported (std::vector layer_names); + +} // namespace detail + +struct Instance +{ + VkInstance instance = VK_NULL_HANDLE; + VkAllocationCallbacks* allocator = VK_NULL_HANDLE; + VkDebugUtilsMessengerEXT debug_messenger = VK_NULL_HANDLE; + bool headless = false; + bool validation_enabled = false; + bool debug_callback_enabled = false; +}; + +void destroy_instance (Instance instance); // release instance resources + +class InstanceBuilder +{ + public: + detail::Expected build (); // use builder pattern + + InstanceBuilder& set_app_name (std::string app_name); + InstanceBuilder& set_engine_name (std::string engine_name); + + InstanceBuilder& set_app_version (uint32_t major, uint32_t minor, uint32_t patch); + InstanceBuilder& set_engine_version (uint32_t major, uint32_t minor, uint32_t patch); + InstanceBuilder& set_api_version (uint32_t major, uint32_t minor, uint32_t patch); + + InstanceBuilder& add_layer (std::string app_name); + InstanceBuilder& add_extension (std::string app_name); + + InstanceBuilder& setup_validation_layers (bool enable_validation = true); + InstanceBuilder& set_headless (bool headless = false); + + InstanceBuilder& set_default_debug_messenger (); + InstanceBuilder& set_debug_callback (PFN_vkDebugUtilsMessengerCallbackEXT callback); + InstanceBuilder& set_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity); + InstanceBuilder& add_debug_messenger_severity (VkDebugUtilsMessageSeverityFlagsEXT severity); + InstanceBuilder& set_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type); + InstanceBuilder& add_debug_messenger_type (VkDebugUtilsMessageTypeFlagsEXT type); + + InstanceBuilder& add_validation_disable (VkValidationCheckEXT check); + InstanceBuilder& add_validation_feature_enable (VkValidationFeatureEnableEXT enable); + InstanceBuilder& add_validation_feature_disable (VkValidationFeatureDisableEXT disable); + + InstanceBuilder& set_allocator_callback (VkAllocationCallbacks* allocator); + + private: + struct InstanceInfo + { + // VkApplicationInfo + std::string app_name; + std::string engine_name; + uint32_t application_version = 0; + uint32_t engine_version = 0; + uint32_t api_version = VK_MAKE_VERSION (1, 0, 0); + + // VkInstanceCreateInfo + std::vector layers; + std::vector extensions; + VkInstanceCreateFlags flags = 0; + void* pNext = nullptr; + VkAllocationCallbacks* allocator = nullptr; + + // debug callback + PFN_vkDebugUtilsMessengerCallbackEXT debug_callback = nullptr; + VkDebugUtilsMessageSeverityFlagsEXT debug_message_severity = + VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + VkDebugUtilsMessageTypeFlagsEXT debug_message_type = + VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | + VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; + + // validation features + std::vector disabled_validation_checks; + std::vector enabled_validation_features; + std::vector disabled_validation_features; + + // booleans + bool ignore_non_critical_issues = true; + bool enable_validation_layers = false; + bool use_debug_messenger = false; + bool headless_context = false; + } info; +}; + +} // namespace vkbs \ No newline at end of file diff --git a/src/Swapchain.cpp b/src/Swapchain.cpp new file mode 100644 index 0000000..ca5ae93 --- /dev/null +++ b/src/Swapchain.cpp @@ -0,0 +1,154 @@ +#include "Swapchain.h" + +namespace vkbs +{ +namespace detail +{ +VkSurfaceFormatKHR choose_swapchain_surface_format (std::vector const& availableFormats) +{ + for (const auto& availableFormat : availableFormats) + { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + { + return availableFormat; + } + } + + return availableFormats[0]; +} + +VkPresentModeKHR choose_swap_present_mode (std::vector const& availablePresentModes) +{ + for (const auto& availablePresentMode : availablePresentModes) + { + if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) + { + return availablePresentMode; + } + } + + return VK_PRESENT_MODE_FIFO_KHR; +} + +VkExtent2D choose_swap_extent ( + VkSurfaceCapabilitiesKHR const& capabilities, uint32_t desired_width, uint32_t desired_height) +{ + if (capabilities.currentExtent.width != UINT32_MAX) + { + return capabilities.currentExtent; + } + else + { + const int WIDTH = 800; + const int HEIGHT = 600; + VkExtent2D actualExtent = { WIDTH, HEIGHT }; + + actualExtent.width = std::max (capabilities.minImageExtent.width, + std::min (capabilities.maxImageExtent.width, actualExtent.width)); + actualExtent.height = std::max (capabilities.minImageExtent.height, + std::min (capabilities.maxImageExtent.height, actualExtent.height)); + + return actualExtent; + } +} +} // namespace detail +detail::Expected SwapchainBuilder::build () +{ + auto surface_support = + detail::query_surface_support_details (info.physical_device.phys_device, info.surface); + if (!surface_support.has_value ()) + return detail::Error{ surface_support.error ().error_code, "can't get surface support" }; + VkSurfaceFormatKHR surfaceFormat = + detail::choose_swapchain_surface_format (surface_support.value ().formats); + VkPresentModeKHR presentMode = detail::choose_swap_present_mode (surface_support.value ().present_modes); + VkExtent2D extent = detail::choose_swap_extent (surface_support.value ().capabilities); + + uint32_t imageCount = surface_support.value ().capabilities.minImageCount + 1; + if (surface_support.value ().capabilities.maxImageCount > 0 && + imageCount > surface_support.value ().capabilities.maxImageCount) + { + imageCount = surface_support.value ().capabilities.maxImageCount; + } + + VkSwapchainCreateInfoKHR swapchain_create_info = {}; + swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; + swapchain_create_info.surface = info.surface; + + swapchain_create_info.minImageCount = imageCount; + swapchain_create_info.imageFormat = surfaceFormat.format; + swapchain_create_info.imageColorSpace = surfaceFormat.colorSpace; + swapchain_create_info.imageExtent = extent; + swapchain_create_info.imageArrayLayers = 1; + swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + + detail::QueueFamilies indices = + detail::find_queue_families (info.physical_device.phys_device, info.surface); + uint32_t queueFamilyIndices[] = { static_cast (indices.graphics), + static_cast (indices.present) }; + + if (indices.graphics != indices.present) + { + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; + swapchain_create_info.queueFamilyIndexCount = 2; + swapchain_create_info.pQueueFamilyIndices = queueFamilyIndices; + } + else + { + swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; + } + + swapchain_create_info.preTransform = surface_support.value ().capabilities.currentTransform; + swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + swapchain_create_info.presentMode = presentMode; + swapchain_create_info.clipped = VK_TRUE; + swapchain_create_info.oldSwapchain = info.old_swapchain; + Swapchain swapchain; + VkResult res = vkCreateSwapchainKHR (info.device, &swapchain_create_info, nullptr, &swapchain.swapchain); + if (res != VK_SUCCESS) + { + return detail::Error{ res, "Failed to create swapchain" }; + } + auto swapchain_images = + detail::get_vector (vkGetSwapchainImagesKHR, info.device, swapchain.swapchain); + + swapchain.image_format = surfaceFormat.format; + swapchain.extent = extent; + + return swapchain; +} +detail::Expected SwapchainBuilder::recreate (Swapchain const& swapchain) +{ + info.old_swapchain = swapchain.swapchain; + return build (); +} + +void SwapchainBuilder::destroy (Swapchain const& swapchain) +{ + vkDestroySwapchainKHR (swapchain.device, swapchain.swapchain, swapchain.allocator); +} + + +SwapchainBuilder& SwapchainBuilder::set_desired_format (VkFormat format) +{ + info.desired_format = format; + return *this; +} +SwapchainBuilder& SwapchainBuilder::set_fallback_format (VkFormat format) +{ + info.fallback_format = format; + return *this; +} +SwapchainBuilder& SwapchainBuilder::set_desired_present_mode (VkPresentModeKHR present_mode) +{ + info.desired_present_mode = present_mode; + return *this; +} +SwapchainBuilder& SwapchainBuilder::set_fallback_present_mode (VkPresentModeKHR present_mode) +{ + info.fallback_present_mode = present_mode; + return *this; +} + + + +} // namespace vkbs \ No newline at end of file diff --git a/src/Swapchain.h b/src/Swapchain.h new file mode 100644 index 0000000..3857f86 --- /dev/null +++ b/src/Swapchain.h @@ -0,0 +1,58 @@ +#pragma once + +#include "Util.h" +#include "Instance.h" +#include "Device.h" + +namespace vkbs +{ +namespace detail +{ +VkSurfaceFormatKHR choose_swapchain_surface_format (std::vector const& availableFormats); +VkPresentModeKHR choose_swap_present_mode (std::vector const& availablePresentModes); +VkExtent2D choose_swap_extent (VkSurfaceCapabilitiesKHR const& capabilities); +} // namespace detail + +struct Swapchain +{ + VkDevice device = VK_NULL_HANDLE; + VkSwapchainKHR swapchain = VK_NULL_HANDLE; + VkAllocationCallbacks* allocator = VK_NULL_HANDLE; + std::vector images; + VkFormat image_format = VK_FORMAT_UNDEFINED; + VkExtent2D extent = { 0, 0 }; +}; + +class SwapchainBuilder +{ + public: + detail::Expected build (); + detail::Expected recreate (Swapchain const& swapchain); + void destroy (Swapchain const& swapchain); + + SwapchainBuilder& set_device (Device const& device); + + SwapchainBuilder& set_desired_format (VkFormat format); + SwapchainBuilder& set_fallback_format (VkFormat format); + + SwapchainBuilder& set_desired_present_mode (VkPresentModeKHR present_mode); + SwapchainBuilder& set_fallback_present_mode (VkPresentModeKHR present_mode); + + + private: + struct SwapchainInfo + { + VkDevice device = VK_NULL_HANDLE; + PhysicalDevice physical_device; + VkSurfaceKHR surface = VK_NULL_HANDLE; + VkSwapchainKHR old_swapchain = VK_NULL_HANDLE; + VkFormat desired_format = VK_FORMAT_R8G8B8A8_UNORM; + VkFormat fallback_format = VK_FORMAT_R8G8B8A8_UNORM; + VkPresentModeKHR desired_present_mode = VK_PRESENT_MODE_FIFO_KHR; + VkPresentModeKHR fallback_present_mode = VK_PRESENT_MODE_FIFO_KHR; + std::vector acceptable_present_modes; + uint32_t desired_width = 256; + uint32_t desired_height = 256; + } info; +}; +} // namespace vkbs \ No newline at end of file diff --git a/src/Util.h b/src/Util.h new file mode 100644 index 0000000..209cd7c --- /dev/null +++ b/src/Util.h @@ -0,0 +1,170 @@ +#pragma once + +#include + +#include +#include +#include + +#include + +namespace vkbs +{ + +namespace detail +{ + +template struct Error +{ + T error_code; + const char* msg; +}; +template class Expected +{ + public: + Expected (const E& expect) : m_expect{ expect }, m_init{ true } {} + Expected (E&& expect) : m_expect{ std::move (expect) }, m_init{ true } {} + Expected (const Error& error) : m_error{ error }, m_init{ false } {} + Expected (Error&& error) : m_error{ std::move (error) }, m_init{ false } {} + ~Expected () { destroy (); } + Expected (Expected const& expected) : m_init (expected.m_init) + { + if (m_init) + new (&m_expect) E{ expected.m_expect }; + else + new (&m_error) Error{ expected.m_error }; + } + Expected (Expected&& expected) : m_init (expected.m_init) + { + if (m_init) + new (&m_expect) E{ std::move (expected.m_expect) }; + else + new (&m_error) Error{ std::move (expected.m_error) }; + expected.destroy (); + } + + Expected& operator= (const E& expect) + { + destroy (); + m_init = true; + new (&m_expect) E{ expect }; + return *this; + } + Expected& operator= (E&& expect) + { + destroy (); + m_init = true; + new (&m_expect) E{ std::move (expect) }; + return *this; + } + Expected& operator= (const Error& error) + { + destroy (); + m_init = false; + new (&m_error) Error{ error }; + return *this; + } + Expected& operator= (Error&& error) + { + destroy (); + m_init = false; + new (&m_error) Error{ std::move (error) }; + return *this; + } + // clang-format off + const E* operator-> () const { assert (m_init); return &m_expect; } + E* operator-> () { assert (m_init); return &m_expect; } + const E& operator* () const& { assert (m_init); return m_expect; } + E& operator* () & { assert (m_init); return m_expect; } + E&& operator* () && { assert (m_init); return std::move (m_expect); } + const E& value () const& { assert (m_init); return m_expect; } + E& value () & { assert (m_init); return m_expect; } + const E&& value () const&& { assert (m_init); return std::move (m_expect); } + E&& value () && { assert (m_init); return std::move (m_expect); } + const Error& error () const& { assert (!m_init); return m_error; } + Error& error () & { assert (!m_init); return m_error; } + const Error&& error () const&& { assert (!m_init); return std::move (m_error); } + Error&& error () && { assert (!m_init); return move (m_error); } + // clang-format on + bool has_value () const { return m_init; } + explicit operator bool () const { return m_init; } + + private: + void destroy () + { + if (m_init) + m_expect.~E (); + else + m_error.~Error (); + } + union + { + E m_expect; + Error m_error; + }; + bool m_init; +}; + +/* TODO implement operator == and operator != as friend or global */ + + +// Helper for robustly executing the two-call pattern +template +auto get_vector_init (F&& f, T init, Ts&&... ts) -> Expected, VkResult> +{ + uint32_t count = 0; + std::vector results; + VkResult err; + do + { + err = f (ts..., &count, nullptr); + if (err) + { + return Error{ err, "" }; + }; + results.resize (count, init); + err = f (ts..., &count, results.data ()); + } while (err == VK_INCOMPLETE); + if (err) + { + return Error{ err, "" }; + }; + return results; +} + +template +auto get_vector (F&& f, Ts&&... ts) -> Expected, VkResult> +{ + return get_vector_init (f, T (), ts...); +} + +template +auto get_vector_noerror (F&& f, T init, Ts&&... ts) -> std::vector +{ + uint32_t count = 0; + std::vector results; + f (ts..., &count, nullptr); + results.resize (count, init); + f (ts..., &count, results.data ()); + return results; +} +template +auto get_vector_noerror (F&& f, Ts&&... ts) -> std::vector +{ + return get_vector_noerror (f, T (), ts...); +} + +template void pNext_append (VkBaseOutStructure* chain, T* structure) +{ + if (chain == nullptr) return; + + while (chain->pNext != nullptr) + { + chain = chain->pNext; + } + chain->pNext = (VkBaseOutStructure*)structure; +} + +} // namespace detail + +} // namespace vkbs \ No newline at end of file diff --git a/src/VkBootstrap.h b/src/VkBootstrap.h new file mode 100644 index 0000000..61bd673 --- /dev/null +++ b/src/VkBootstrap.h @@ -0,0 +1,5 @@ +#pragma once + +#include "Instance.h" +#include "Device.h" +#include "Swapchain.h" \ No newline at end of file diff --git a/tests/run_tests.cpp b/tests/run_tests.cpp new file mode 100644 index 0000000..eda8664 --- /dev/null +++ b/tests/run_tests.cpp @@ -0,0 +1,181 @@ +#include + +#include "VkBootstrap.h" + +#define GLFW_INCLUDE_VULKAN +#include "GLFW/glfw3.h" + +GLFWwindow* create_window_glfw () +{ + glfwInit (); + glfwWindowHint (GLFW_CLIENT_API, GLFW_NO_API); + return glfwCreateWindow (640, 480, "Window Title", NULL, NULL); +} +void destroy_window_glfw (GLFWwindow* window) +{ + glfwDestroyWindow (window); + glfwTerminate (); +} +VkSurfaceKHR create_surface_glfw (VkInstance instance, GLFWwindow* window) +{ + VkSurfaceKHR surface = nullptr; + VkResult err = glfwCreateWindowSurface (instance, window, NULL, &surface); + if (err) + { + surface = nullptr; + } + return surface; +} +int test_happy_path () +{ + auto window = create_window_glfw (); + + vkbs::InstanceBuilder instance_builder; + auto instance_ret = instance_builder.set_default_debug_messenger ().build (); + if (!instance_ret) return -1; // couldn't make instance + vkbs::Instance instance = instance_ret.value (); + + auto surface = create_surface_glfw (instance.instance, window); + + vkbs::PhysicalDeviceSelector phys_device_selector; + auto phys_device_ret = phys_device_selector.set_instance (instance).set_surface (surface).select (); + if (!phys_device_ret) return -2; // couldn't select physical device + vkbs::PhysicalDevice physical_device = phys_device_ret.value (); + + vkbs::DeviceBuilder device_builder; + auto device_ret = device_builder.set_physical_device (physical_device).build (); + if (!device_ret) return -3; // couldn't create device + vkbs::Device device = device_ret.value (); + + // possible swapchain creation... + + vkbs::destroy_device (device); + vkbs::destroy_instance (instance); + destroy_window_glfw (window); + return 0; +} + + +int test_instance_basic () +{ + + vkbs::InstanceBuilder builder; + + auto instance_ret = + builder.setup_validation_layers () + .set_app_name ("test") + .set_debug_callback ([] (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void* pUserData) -> VkBool32 { + auto ms = vkbs::DebugMessageSeverity (messageSeverity); + auto mt = vkbs::DebugMessageType (messageType); + printf ("[%s: %s](user defined)\n%s\n", ms, mt, pCallbackData->pMessage); + return VK_FALSE; + }) + .set_api_version (1, 2, 111) + .build (); + if (!instance_ret.has_value ()) + { + return 1; + } + return 0; +} + +int test_instance_headless () +{ + + vkbs::InstanceBuilder builder; + + auto instance_ret = builder.setup_validation_layers () + .set_headless () + .set_app_version (4, 5, 6) + .set_app_name ("headless") + .set_engine_name ("nick") + .set_api_version (1, 0, 34) + .set_default_debug_messenger () + .build (); + if (!instance_ret.has_value ()) + { + return 1; + } + return 0; +} + +int test_physical_device_selection () +{ + vkbs::InstanceBuilder instance_builder; + auto instance_ret = instance_builder.set_default_debug_messenger ().build (); + auto instance = instance_ret.value (); + auto window = create_window_glfw (); + auto surface = create_surface_glfw (instance.instance, window); + + vkbs::PhysicalDeviceSelector selector; + auto phys_dev_ret = selector.set_instance (instance) + .set_surface (surface) + .add_desired_extension (VK_KHR_MULTIVIEW_EXTENSION_NAME) + .add_required_extension (VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME) + .set_minimum_version (1, 0) + .set_desired_version (1, 1) + .select (); + if (!phys_dev_ret.has_value ()) + { + return -1; + } + vkbs::destroy_instance (instance); + destroy_window_glfw (window); + return 0; +} + +int test_device_creation () +{ + vkbs::InstanceBuilder instance_builder; + auto instance_ret = instance_builder.set_default_debug_messenger ().build (); + auto instance = instance_ret.value (); + auto window = create_window_glfw (); + auto surface = create_surface_glfw (instance.instance, window); + + vkbs::PhysicalDeviceSelector selector; + auto phys_dev_ret = selector.set_instance (instance).set_surface (surface).select (); + auto phys_dev = phys_dev_ret.value (); + + vkbs::DeviceBuilder device_builder; + auto dev_ret = device_builder.set_physical_device (phys_dev).build (); + if (!dev_ret.has_value ()) + { + printf ("%s\n", dev_ret.error ().msg); + return -1; + } + + vkbs::destroy_device (dev_ret.value ()); + vkbs::destroy_instance (instance); + destroy_window_glfw (window); + return 0; +} + +int main () +{ + printf ("happy path\n"); + test_happy_path (); + + printf ("\nbasic instance\n"); + { + auto ret = test_instance_basic (); + if (ret != 0) printf ("test_failed\n"); + } + printf ("\nheadless instance\n"); + { + auto ret = test_instance_headless (); + if (ret != 0) printf ("test_failed\n"); + } + printf ("\nphysical device selection\n"); + { + auto ret = test_physical_device_selection (); + if (ret != 0) printf ("test_failed\n"); + } + printf ("\ndevice creation\n"); + { + auto ret = test_device_creation (); + if (ret != 0) printf ("test_failed\n"); + } +} \ No newline at end of file