From 1f462b42f3c2555cfb4fcb399a3f6b540a9e7179 Mon Sep 17 00:00:00 2001 From: Charles Giessen Date: Sat, 18 Apr 2020 21:24:59 -0600 Subject: [PATCH] Added many tests and fixed up api as a consequence Added device::get_queue_families() to facilitate custom queue setups get_swapchain_images() renamed to get_image(), made member function to swapchain. get_swapchain_image_views() renamed to get_image_views(), also moved to member function of swapchain fixed bug with headless instance not being correct Stopped Catch2 from polluting the buildable targets --- example/triangle.cpp | 6 +- ext/CMakeLists.txt | 6 + src/VkBootstrap.cpp | 61 +++++++--- src/VkBootstrap.h | 33 ++++-- tests/run_tests.cpp | 269 ++++++++++++++++++++++++++++++++++++++----- tests/test.cpp | 8 -- 6 files changed, 309 insertions(+), 74 deletions(-) delete mode 100644 tests/test.cpp diff --git a/example/triangle.cpp b/example/triangle.cpp index fa7e4fa..dcd0dab 100644 --- a/example/triangle.cpp +++ b/example/triangle.cpp @@ -294,10 +294,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) { } int create_framebuffers (Init& init, RenderData& data) { - data.swapchain_images = vkb::get_swapchain_images (init.swapchain).value (); - // init.swapchain.image_count = data.swapchain_images.size (); - data.swapchain_image_views = - vkb::get_swapchain_image_views (init.swapchain, data.swapchain_images).value (); + data.swapchain_images = init.swapchain.get_images ().value (); + data.swapchain_image_views = init.swapchain.get_image_views ().value (); data.framebuffers.resize (data.swapchain_image_views.size ()); diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index 4708fa3..4daea29 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -11,4 +11,10 @@ set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) add_subdirectory(glfw) + +set(CATCH_BUILD_TESTING OFF CACHE BOOL "" FORCE) +set(CATCH_ENABLE_WERROR OFF CACHE BOOL "" FORCE) +set(CATCH_INSTALL_DOCS OFF CACHE BOOL "" FORCE) +set(CATCH_INSTALL_HELPERS OFF CACHE BOOL "" FORCE) +set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) #remove Catch2 target spam add_subdirectory(Catch2) \ No newline at end of file diff --git a/src/VkBootstrap.cpp b/src/VkBootstrap.cpp index f0199c9..d349556 100644 --- a/src/VkBootstrap.cpp +++ b/src/VkBootstrap.cpp @@ -939,6 +939,9 @@ bool PhysicalDevice::has_dedicated_transfer_queue () const { bool PhysicalDevice::has_separate_transfer_queue () const { return detail::get_separate_transfer_queue_index (queue_families) >= 0; } +std::vector PhysicalDevice::get_queue_families () const { + return queue_families; +} // ---- Queues ---- // @@ -1002,6 +1005,11 @@ detail::Expected> Device::get_dedicated_queue // ---- Device ---- // +CustomQueueDescription::CustomQueueDescription (uint32_t index, uint32_t count, std::vector priorities) +: index (index), count (count), priorities (priorities) { + assert (count == priorities.size ()); +} + void destroy_device (Device device) { vkDestroyDevice (device.device, device.allocation_callbacks); } @@ -1039,6 +1047,14 @@ detail::Expected> DeviceBuilder::build () con std::vector extensions = info.extensions_to_enable; if (info.surface != VK_NULL_HANDLE) extensions.push_back ({ VK_KHR_SWAPCHAIN_EXTENSION_NAME }); + // VUID-VkDeviceCreateInfo-pNext-00373 - don't add pEnabledFeatures if the phys_dev_features_2 is present + bool has_phys_dev_features_2 = false; + for (auto& pNext_struct : info.pNext_chain) { + if (pNext_struct->sType == VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2) { + has_phys_dev_features_2 = true; + } + } + VkDeviceCreateInfo device_create_info = {}; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; detail::setup_pNext_chain (device_create_info, info.pNext_chain); @@ -1047,7 +1063,9 @@ detail::Expected> DeviceBuilder::build () con 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.features; + if (!has_phys_dev_features_2) { + device_create_info.pEnabledFeatures = &info.features; + } Device device; VkResult res = vkCreateDevice (info.physical_device.physical_device, @@ -1067,15 +1085,13 @@ DeviceBuilder& DeviceBuilder::custom_queue_setup (std::vector DeviceBuilder& DeviceBuilder::add_pNext (T* structure) { - info.pNext_chain.push_back (reinterpret_cast (structure)); - return *this; -} DeviceBuilder& DeviceBuilder::set_allocation_callbacks (VkAllocationCallbacks* callbacks) { info.allocation_callbacks = callbacks; return *this; } +// ---- Swapchain ---- // + namespace detail { struct SurfaceSupportDetails { VkSurfaceCapabilitiesKHR capabilities; @@ -1245,7 +1261,10 @@ detail::Expected> SwapchainBuilder::bui swapchain.device = info.device; swapchain.image_format = surface_format.format; swapchain.extent = extent; - auto images = get_swapchain_images (swapchain); + auto images = swapchain.get_images (); + if (!images) { + return detail::Error{ SwapchainError::failed_get_swapchain_images }; + } swapchain.image_count = static_cast (images.value ().size ()); swapchain.allocation_callbacks = info.allocation_callbacks; return swapchain; @@ -1254,27 +1273,28 @@ detail::Expected> SwapchainBuilder::rec Swapchain const& swapchain) const { return build (swapchain.swapchain); } -detail::Expected, detail::Error> get_swapchain_images ( - Swapchain const& swapchain) { - auto swapchain_images = - detail::get_vector (vkGetSwapchainImagesKHR, swapchain.device, swapchain.swapchain); +detail::Expected, detail::Error> Swapchain::get_images () { + auto swapchain_images = detail::get_vector (vkGetSwapchainImagesKHR, device, swapchain); if (!swapchain_images) { return detail::Error{ SwapchainError::failed_get_swapchain_images, swapchain_images.error () }; } return swapchain_images.value (); } +detail::Expected, detail::Error> Swapchain::get_image_views () { -detail::Expected, detail::Error> -get_swapchain_image_views (Swapchain const& swapchain, std::vector const& images) { - std::vector views{ swapchain.image_count }; + auto swapchain_images_ret = get_images (); + if (!swapchain_images_ret) return swapchain_images_ret.error (); + auto swapchain_images = swapchain_images_ret.value (); - for (size_t i = 0; i < swapchain.image_count; i++) { + std::vector views{ swapchain_images.size () }; + + for (size_t i = 0; i < swapchain_images.size (); i++) { VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = images[i]; + createInfo.image = swapchain_images[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = swapchain.image_format; + createInfo.format = image_format; createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; @@ -1285,14 +1305,17 @@ get_swapchain_image_views (Swapchain const& swapchain, std::vector cons createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; - VkResult res = vkCreateImageView (swapchain.device, &createInfo, nullptr, &views[i]); + VkResult res = vkCreateImageView (device, &createInfo, allocation_callbacks, &views[i]); if (res != VK_SUCCESS) return detail::Error{ SwapchainError::failed_create_swapchain_image_views, res }; } return views; } - - +void Swapchain::destroy_image_views (std::vector const& image_views) { + for (auto& image_view : image_views) { + vkDestroyImageView (device, image_view, allocation_callbacks); + } +} void destroy_swapchain (Swapchain const& swapchain) { if (swapchain.device != VK_NULL_HANDLE && swapchain.swapchain != VK_NULL_HANDLE) vkDestroySwapchainKHR (swapchain.device, swapchain.swapchain, swapchain.allocation_callbacks); diff --git a/src/VkBootstrap.h b/src/VkBootstrap.h index b4f2f18..1593128 100644 --- a/src/VkBootstrap.h +++ b/src/VkBootstrap.h @@ -197,8 +197,8 @@ class InstanceBuilder { // Adds an extension to be enabled. Will fail to create an instance if the extension isn't available. InstanceBuilder& enable_extension (const char* extension_name); - // Headless Mode does not load the required extensions for presentation. Defaults to false. - InstanceBuilder& set_headless (bool headless = false); + // Headless Mode does not load the required extensions for presentation. Defaults to true. + InstanceBuilder& set_headless (bool headless = true); // Enables the validation layers. Will fail to create an instance if the validation layers aren't available. InstanceBuilder& enable_validation_layers (bool require_validation = true); @@ -311,6 +311,9 @@ struct PhysicalDevice { // Has a queue family that supports transfer operations but not graphics. bool has_separate_transfer_queue () const; + // Advanced: Get the VkQueueFamilyProperties of the device if special queue setup is needed + std::vector get_queue_families () const; + private: std::vector extensions_to_enable; std::vector queue_families; @@ -450,16 +453,16 @@ struct Device { detail::Expected> get_dedicated_queue (QueueType type) const; }; -void destroy_device (Device device); - +// For advanced device queue setup struct CustomQueueDescription { - CustomQueueDescription (uint32_t index, uint32_t count, std::vector priorities) - : index (index), count (count), priorities (priorities) {} + CustomQueueDescription (uint32_t index, uint32_t count, std::vector priorities); uint32_t index = 0; uint32_t count = 0; std::vector priorities; }; +void destroy_device (Device device); + class DeviceBuilder { public: // Any features and extensions that are requested/required in PhysicalDeviceSelector are automatically enabled. @@ -473,7 +476,10 @@ class DeviceBuilder { // Add a structure to the pNext chain of VkDeviceCreateInfo. // The structure must be valid when DeviceBuilder::build() is called. - template DeviceBuilder& add_pNext (T* structure); + template DeviceBuilder& add_pNext (T* structure) { + info.pNext_chain.push_back (reinterpret_cast (structure)); + return *this; + } // Provide custom allocation callbacks. DeviceBuilder& set_allocation_callbacks (VkAllocationCallbacks* callbacks); @@ -500,15 +506,18 @@ struct Swapchain { VkFormat image_format = VK_FORMAT_UNDEFINED; VkExtent2D extent = { 0, 0 }; VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE; + + // Returns a vector of VkImage handles to the swapchain + detail::Expected, detail::Error> get_images (); + + // Returns a vector of VkImageView's to the VkImage's of the swapchain + // VkImageViews must be destroyed + detail::Expected, detail::Error> get_image_views (); + void destroy_image_views (std::vector const& image_views); }; void destroy_swapchain (Swapchain const& swapchain); -detail::Expected, detail::Error> get_swapchain_images ( - Swapchain const& swapchain); -detail::Expected, detail::Error> -get_swapchain_image_views (Swapchain const& swapchain, std::vector const& images); - class SwapchainBuilder { public: SwapchainBuilder (Device const& device); diff --git a/tests/run_tests.cpp b/tests/run_tests.cpp index 080cc85..66e2879 100644 --- a/tests/run_tests.cpp +++ b/tests/run_tests.cpp @@ -5,8 +5,7 @@ // TODO // Getting queues // get dedicated vs distinct compute queues -// Swapchain creation -// Swapchain recreation + // changing present modes and/or image formats TEST_CASE ("Instance with surface") { @@ -55,45 +54,253 @@ TEST_CASE ("Instance with surface") { } } -TEST_CASE ("basic instance") { +TEST_CASE ("instance configuration") { + SECTION ("custom debug callback") { + vkb::InstanceBuilder builder; + + auto instance_ret = + builder.request_validation_layers () + .set_app_name ("test app") + .set_app_version (1, 0, 0) + .set_engine_name ("engine_name") + .set_engine_version (9, 9, 9) + .set_debug_callback ([] (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, + VkDebugUtilsMessageTypeFlagsEXT messageType, + const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, + void * + /*pUserData*/) -> VkBool32 { + auto ms = vkb::to_string_message_severity (messageSeverity); + auto mt = vkb::to_string_message_type (messageType); + printf ("[%s: %s](user defined)\n%s\n", ms, mt, pCallbackData->pMessage); + return VK_FALSE; + }) + .build (); + + REQUIRE (instance_ret.has_value ()); + + vkb::destroy_instance (instance_ret.value ()); + } + SECTION ("Validation configuration") { + vkb::InstanceBuilder builder; + + auto instance_ret = + builder.request_validation_layers () + .require_api_version (1, 0, 34) + .use_default_debug_messenger () + .add_validation_feature_enable (VkValidationFeatureEnableEXT::VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT) + .add_validation_feature_disable ( + VkValidationFeatureDisableEXT::VK_VALIDATION_FEATURE_DISABLE_OBJECT_LIFETIMES_EXT) + .add_validation_disable (VkValidationCheckEXT::VK_VALIDATION_CHECK_SHADERS_EXT) + .build (); + REQUIRE (instance_ret.has_value ()); + vkb::destroy_instance (instance_ret.value ()); + } +} + +TEST_CASE ("Headless Vulkan") { vkb::InstanceBuilder builder; - auto instance_ret = - builder.request_validation_layers () - .set_app_name ("test") - .set_debug_callback ([] (VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, - VkDebugUtilsMessageTypeFlagsEXT messageType, - const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, - void * - /*pUserData*/) -> VkBool32 { - auto ms = vkb::to_string_message_severity (messageSeverity); - auto mt = vkb::to_string_message_type (messageType); - printf ("[%s: %s](user defined)\n%s\n", ms, mt, pCallbackData->pMessage); - return VK_FALSE; - }) - .require_api_version (1, 2, 111) - .build (); - + auto instance_ret = builder.request_validation_layers ().set_headless ().build (); REQUIRE (instance_ret.has_value ()); + vkb::PhysicalDeviceSelector phys_device_selector (instance_ret.value ()); + auto phys_device_ret = phys_device_selector.select (); + REQUIRE (phys_device_ret.has_value ()); + auto phys_device = phys_device_ret.value (); + + vkb::DeviceBuilder device_builder (phys_device); + auto device_ret = device_builder.build (); + REQUIRE (device_ret.has_value ()); + vkb::destroy_device (device_ret.value ()); + vkb::destroy_instance (instance_ret.value ()); } -TEST_CASE ("headless instance") { +TEST_CASE ("Device Configuration") { + + auto window = create_window_glfw (); + vkb::InstanceBuilder builder; + + auto instance_ret = builder.request_validation_layers ().build (); + REQUIRE (instance_ret.has_value ()); + auto surface = create_surface_glfw (instance_ret.value ().instance, window); + + vkb::PhysicalDeviceSelector phys_device_selector (instance_ret.value ()); + + auto phys_device_ret = phys_device_selector.set_surface (surface).select (); + REQUIRE (phys_device_ret.has_value ()); + auto phys_device = phys_device_ret.value (); + + + SECTION ("Custom queue setup") { + std::vector queue_descriptions; + auto queue_families = phys_device.get_queue_families (); + for (uint32_t i = 0; i < (uint32_t)queue_families.size (); i++) { + if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + queue_descriptions.push_back (vkb::CustomQueueDescription ( + i, queue_families[i].queueCount, std::vector (queue_families[i].queueCount, 1.0f))); + } + } + if (phys_device.has_dedicated_compute_queue ()) { + for (uint32_t i = 0; i < (uint32_t)queue_families.size (); i++) { + if ((queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT) && + (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0 && + (queue_families[i].queueFlags & VK_QUEUE_TRANSFER_BIT) == 0) + queue_descriptions.push_back (vkb::CustomQueueDescription (i, + queue_families[i].queueCount, + std::vector (queue_families[i].queueCount, 1.0f))); + } + } else if (phys_device.has_separate_compute_queue ()) { + for (uint32_t i = 0; i < (uint32_t)queue_families.size (); i++) { + if ((queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT) && + ((queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) == 0)) { + queue_descriptions.push_back (vkb::CustomQueueDescription (i, + queue_families[i].queueCount, + std::vector (queue_families[i].queueCount, 1.0f))); + } + } + } + + vkb::DeviceBuilder device_builder (phys_device); + auto device_ret = device_builder.custom_queue_setup (queue_descriptions).build (); + REQUIRE (device_ret.has_value ()); + vkb::destroy_device (device_ret.value ()); + } + + SECTION ("VkPhysicalDeviceFeatures2 in pNext Chain") { + VkPhysicalDeviceFeatures2 phys_dev_feat_2{}; + + vkb::DeviceBuilder device_builder (phys_device); + auto device_ret = device_builder.add_pNext (&phys_dev_feat_2).build (); + REQUIRE (device_ret.has_value ()); + vkb::destroy_device (device_ret.value ()); + } + + destroy_surface (instance_ret.value ().instance, surface); + vkb::destroy_instance (instance_ret.value ()); +} + +TEST_CASE ("Swapchain") { + GIVEN ("A working instance, window, surface, and device") { + auto window = create_window_glfw (); + vkb::InstanceBuilder builder; + + auto instance_ret = builder.request_validation_layers ().build (); + REQUIRE (instance_ret.has_value ()); + auto surface = create_surface_glfw (instance_ret.value ().instance, window); + + vkb::PhysicalDeviceSelector phys_device_selector (instance_ret.value ()); + auto phys_device_ret = phys_device_selector.set_surface (surface).select (); + REQUIRE (phys_device_ret.has_value ()); + auto phys_device = phys_device_ret.value (); + + vkb::DeviceBuilder device_builder (phys_device); + auto device_ret = device_builder.build (); + REQUIRE (device_ret.has_value ()); + vkb::Device device = device_ret.value (); + + THEN ("Swapchain can be made") { + vkb::SwapchainBuilder swapchain_builder (device); + auto swapchain_ret = swapchain_builder.build (); + REQUIRE (swapchain_ret.has_value ()); + auto swapchain = swapchain_ret.value (); + + THEN ("Acquire swapchain images and views") { + auto images = swapchain.get_images (); + REQUIRE (images.has_value ()); + REQUIRE (images.value ().size () > 0); + + auto image_views = swapchain.get_image_views (); + REQUIRE (image_views.has_value ()); + REQUIRE (image_views.value ().size () > 0); + swapchain.destroy_image_views (image_views.value ()); + } + + vkb::destroy_swapchain (swapchain_ret.value ()); + } + + AND_THEN ("Swapchain configuration") { + vkb::SwapchainBuilder swapchain_builder (device); + auto swapchain_ret = + swapchain_builder.set_desired_extent (256, 256) + .set_desired_format ({ VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR }) + .set_desired_present_mode (VK_PRESENT_MODE_IMMEDIATE_KHR) + .build (); + REQUIRE (swapchain_ret.has_value ()); + + vkb::destroy_swapchain (swapchain_ret.value ()); + } + AND_THEN ("Swapchain defaults can be used") { + vkb::SwapchainBuilder swapchain_builder (device); + auto swapchain_ret = swapchain_builder.use_default_format_selection () + .use_default_present_mode_selection () + .build (); + REQUIRE (swapchain_ret.has_value ()); + + vkb::destroy_swapchain (swapchain_ret.value ()); + } + AND_THEN ("Swapchain can be recreated") { + vkb::SwapchainBuilder swapchain_builder (device); + auto swapchain_ret = swapchain_builder.build (); + REQUIRE (swapchain_ret.has_value ()); + + auto swapchain = swapchain_ret.value (); + auto recreated_swapchain_ret = swapchain_builder.recreate (swapchain); + REQUIRE (recreated_swapchain_ret.has_value ()); + + vkb::destroy_swapchain (recreated_swapchain_ret.value ()); + } + + vkb::destroy_device (device_ret.value ()); + destroy_surface (instance_ret.value ().instance, surface); + vkb::destroy_instance (instance_ret.value ()); + } +} + +void* VKAPI_PTR shim_vkAllocationFunction ( + void* /*pUserData*/, size_t size, size_t /*alignment*/, VkSystemAllocationScope /*allocationScope*/) { + return malloc (size); +} +void* VKAPI_PTR shim_vkReallocationFunction ( + void* /*pUserData*/, void* pOriginal, size_t size, size_t /*alignment*/, VkSystemAllocationScope /*allocationScope*/) { + return realloc (pOriginal, size); +} +void VKAPI_PTR shim_vkFreeFunction (void* /*pUserData*/, void* pMemory) { return free (pMemory); } + + +TEST_CASE ("Allocation Callbacks") { + VkAllocationCallbacks allocation_callbacks{}; + allocation_callbacks.pfnAllocation = &shim_vkAllocationFunction; + allocation_callbacks.pfnReallocation = &shim_vkReallocationFunction; + allocation_callbacks.pfnFree = &shim_vkFreeFunction; + + auto window = create_window_glfw (); vkb::InstanceBuilder builder; auto instance_ret = - builder.request_validation_layers () - .set_headless () - .set_app_version (4, 5, 6) - .set_app_name ("headless") - .set_engine_name ("nick") - .require_api_version (1, 0, 34) - .use_default_debug_messenger () - .add_validation_feature_enable (VkValidationFeatureEnableEXT::VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT) - .add_validation_feature_disable (VkValidationFeatureDisableEXT::VK_VALIDATION_FEATURE_DISABLE_OBJECT_LIFETIMES_EXT) - .add_validation_disable (VkValidationCheckEXT::VK_VALIDATION_CHECK_SHADERS_EXT) - .build (); + builder.request_validation_layers ().set_allocation_callbacks (&allocation_callbacks).build (); REQUIRE (instance_ret.has_value ()); + auto surface = create_surface_glfw (instance_ret.value ().instance, window); + + vkb::PhysicalDeviceSelector phys_device_selector (instance_ret.value ()); + + auto phys_device_ret = phys_device_selector.set_surface (surface).select (); + REQUIRE (phys_device_ret.has_value ()); + auto phys_device = phys_device_ret.value (); + + vkb::DeviceBuilder device_builder (phys_device); + auto device_ret = device_builder.set_allocation_callbacks (&allocation_callbacks).build (); + REQUIRE (device_ret.has_value ()); + vkb::Device device = device_ret.value (); + + vkb::SwapchainBuilder swapchain_builder (device); + auto swapchain_ret = swapchain_builder.set_allocation_callbacks (&allocation_callbacks).build (); + REQUIRE (swapchain_ret.has_value ()); + auto swapchain = swapchain_ret.value (); + + vkb::destroy_swapchain (swapchain_ret.value ()); + vkb::destroy_device (device_ret.value ()); + + destroy_surface (instance_ret.value ().instance, surface); vkb::destroy_instance (instance_ret.value ()); } diff --git a/tests/test.cpp b/tests/test.cpp deleted file mode 100644 index 74242ee..0000000 --- a/tests/test.cpp +++ /dev/null @@ -1,8 +0,0 @@ - -#include - -#include "common.h" - -#include - -#define CATCH_CONFIG_MAIN