Unload drivers which report 0 physical devices
authorCharles Giessen <charles@lunarg.com>
Tue, 7 Nov 2023 21:45:35 +0000 (14:45 -0700)
committerCharles Giessen <46324611+charles-lunarg@users.noreply.github.com>
Mon, 29 Apr 2024 16:53:33 +0000 (10:53 -0600)
The loader did not unload any ICD's which contained zero physical devices, which
could cause premature exhaustion of memory in some circumstances, like 32 bit
applications. While the policy of the loader has been to keep things open for
the duration of the instance, these ICD's don't meaningfully participate in
anything due to the lack of VkPhysicalDevices.

This change adds a check after vkEnumeratePhysicalDevices where pPhysicalDevices
is not NULL such that all loader_icd_terms which reported zero physical devices
have its vkDestroyInstance called, and removed from the loader_instance's
icd_term linked list.

loader/loader.c
loader/loader.h
loader/loader_common.h
loader/trampoline.c
tests/live_verification/time_dynamic_loading.cpp
tests/loader_regression_tests.cpp

index 93008f3773004bab46fff665dffe2fa21d3f9416..b000fedb7431d1074fa503d7e1baa008eee7d5aa 100644 (file)
@@ -96,7 +96,7 @@ loader_platform_thread_mutex loader_global_instance_list_lock;
 // functionality, but the fact that the libraries already been loaded causes any call that needs to load ICD libraries to speed up
 // significantly. This can have a huge impact when making repeated calls to vkEnumerateInstanceExtensionProperties and
 // vkCreateInstance.
-struct loader_icd_tramp_list scanned_icds;
+struct loader_icd_tramp_list preloaded_icds;
 
 // controls whether loader_platform_close_library() closes the libraries or not - controlled by an environment
 // variables - this is just the definition of the variable, usage is in vk_loader_platform.h
@@ -1339,6 +1339,18 @@ struct loader_icd_term *loader_icd_add(struct loader_instance *ptr_inst, const s
 
     return icd_term;
 }
+// Closes the library handle in the scanned ICD, free the lib_name string, and zeros out all data
+void loader_unload_scanned_icd(struct loader_instance *inst, struct loader_scanned_icd *scanned_icd) {
+    if (NULL == scanned_icd) {
+        return;
+    }
+    if (scanned_icd->handle) {
+        loader_platform_close_library(scanned_icd->handle);
+        scanned_icd->handle = NULL;
+    }
+    loader_instance_heap_free(inst, scanned_icd->lib_name);
+    memset(scanned_icd, 0, sizeof(struct loader_scanned_icd));
+}
 
 // Determine the ICD interface version to use.
 //     @param icd
@@ -1374,7 +1386,7 @@ bool loader_get_icd_interface_version(PFN_vkNegotiateLoaderICDInterfaceVersion f
     return true;
 }
 
-void loader_scanned_icd_clear(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list) {
+void loader_clear_scanned_icd_list(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list) {
     if (0 != icd_tramp_list->capacity && icd_tramp_list->scanned_list) {
         for (uint32_t i = 0; i < icd_tramp_list->count; i++) {
             if (icd_tramp_list->scanned_list[i].handle) {
@@ -1388,14 +1400,14 @@ void loader_scanned_icd_clear(const struct loader_instance *inst, struct loader_
     memset(icd_tramp_list, 0, sizeof(struct loader_icd_tramp_list));
 }
 
-VkResult loader_scanned_icd_init(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list) {
+VkResult loader_init_scanned_icd_list(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list) {
     VkResult res = VK_SUCCESS;
-    loader_scanned_icd_clear(inst, icd_tramp_list);
+    loader_clear_scanned_icd_list(inst, icd_tramp_list);
     icd_tramp_list->capacity = 8 * sizeof(struct loader_scanned_icd);
     icd_tramp_list->scanned_list = loader_instance_heap_alloc(inst, icd_tramp_list->capacity, VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE);
     if (NULL == icd_tramp_list->scanned_list) {
         loader_log(inst, VULKAN_LOADER_ERROR_BIT, 0,
-                   "loader_scanned_icd_init: Realloc failed for layer list when attempting to add new layer");
+                   "loader_init_scanned_icd_list: Realloc failed for layer list when attempting to add new layer");
         res = VK_ERROR_OUT_OF_HOST_MEMORY;
     }
     return res;
@@ -1871,14 +1883,14 @@ void loader_preload_icds(void) {
     loader_platform_thread_lock_mutex(&loader_preload_icd_lock);
 
     // Already preloaded, skip loading again.
-    if (scanned_icds.scanned_list != NULL) {
+    if (preloaded_icds.scanned_list != NULL) {
         loader_platform_thread_unlock_mutex(&loader_preload_icd_lock);
         return;
     }
 
-    VkResult result = loader_icd_scan(NULL, &scanned_icds, NULL, NULL);
+    VkResult result = loader_icd_scan(NULL, &preloaded_icds, NULL, NULL);
     if (result != VK_SUCCESS) {
-        loader_scanned_icd_clear(NULL, &scanned_icds);
+        loader_clear_scanned_icd_list(NULL, &preloaded_icds);
     }
     loader_platform_thread_unlock_mutex(&loader_preload_icd_lock);
 }
@@ -1886,7 +1898,7 @@ void loader_preload_icds(void) {
 // Release the ICD libraries that were preloaded
 void loader_unload_preloaded_icds(void) {
     loader_platform_thread_lock_mutex(&loader_preload_icd_lock);
-    loader_scanned_icd_clear(NULL, &scanned_icds);
+    loader_clear_scanned_icd_list(NULL, &preloaded_icds);
     loader_platform_thread_unlock_mutex(&loader_preload_icd_lock);
 }
 
@@ -3559,7 +3571,7 @@ VkResult loader_icd_scan(const struct loader_instance *inst, struct loader_icd_t
     struct ICDManifestInfo *icd_details = NULL;
 
     // Set up the ICD Trampoline list so elements can be written into it.
-    res = loader_scanned_icd_init(inst, icd_tramp_list);
+    res = loader_init_scanned_icd_list(inst, icd_tramp_list);
     if (res == VK_ERROR_OUT_OF_HOST_MEMORY) {
         return res;
     }
@@ -5518,8 +5530,6 @@ VKAPI_ATTR void VKAPI_CALL terminator_DestroyInstance(VkInstance instance, const
     if (NULL == ptr_instance) {
         return;
     }
-    struct loader_icd_term *icd_terms = ptr_instance->icd_terms;
-    struct loader_icd_term *next_icd_term;
 
     // Remove this instance from the list of instances:
     struct loader_instance *prev = NULL;
@@ -5539,18 +5549,19 @@ VKAPI_ATTR void VKAPI_CALL terminator_DestroyInstance(VkInstance instance, const
     }
     loader_platform_thread_unlock_mutex(&loader_global_instance_list_lock);
 
+    struct loader_icd_term *icd_terms = ptr_instance->icd_terms;
     while (NULL != icd_terms) {
         if (icd_terms->instance) {
             icd_terms->dispatch.DestroyInstance(icd_terms->instance, pAllocator);
         }
-        next_icd_term = icd_terms->next;
+        struct loader_icd_term *next_icd_term = icd_terms->next;
         icd_terms->instance = VK_NULL_HANDLE;
         loader_icd_destroy(ptr_instance, icd_terms, pAllocator);
 
         icd_terms = next_icd_term;
     }
 
-    loader_scanned_icd_clear(ptr_instance, &ptr_instance->icd_tramp_list);
+    loader_clear_scanned_icd_list(ptr_instance, &ptr_instance->icd_tramp_list);
     loader_destroy_generic_list(ptr_instance, (struct loader_generic_list *)&ptr_instance->ext_list);
     if (NULL != ptr_instance->phys_devs_term) {
         for (uint32_t i = 0; i < ptr_instance->phys_dev_count_term; i++) {
@@ -6204,6 +6215,7 @@ VkResult setup_loader_term_phys_devs(struct loader_instance *inst) {
         }
         icd_phys_dev_array[icd_idx].icd_term = icd_term;
         icd_phys_dev_array[icd_idx].icd_index = icd_idx;
+        icd_term->physical_device_count = icd_phys_dev_array[icd_idx].device_count;
         icd_term = icd_term->next;
         ++icd_idx;
     }
@@ -6368,6 +6380,63 @@ out:
 
     return res;
 }
+/**
+ * Iterates through all drivers and unloads any which do not contain physical devices.
+ * This saves address space, which for 32 bit applications is scarce.
+ * This must only be called after a call to vkEnumeratePhysicalDevices that isn't just querying the count
+ */
+void unload_drivers_without_physical_devices(struct loader_instance *inst) {
+    struct loader_icd_term *cur_icd_term = inst->icd_terms;
+    struct loader_icd_term *prev_icd_term = NULL;
+
+    while (NULL != cur_icd_term) {
+        struct loader_icd_term *next_icd_term = cur_icd_term->next;
+        if (cur_icd_term->physical_device_count == 0) {
+            uint32_t cur_scanned_icd_index = UINT32_MAX;
+            if (inst->icd_tramp_list.scanned_list) {
+                for (uint32_t i = 0; i < inst->icd_tramp_list.count; i++) {
+                    if (&(inst->icd_tramp_list.scanned_list[i]) == cur_icd_term->scanned_icd) {
+                        cur_scanned_icd_index = i;
+                        break;
+                    }
+                }
+            }
+            if (cur_scanned_icd_index != UINT32_MAX) {
+                loader_log(inst, VULKAN_LOADER_INFO_BIT | VULKAN_LOADER_DRIVER_BIT, 0,
+                           "Removing driver %s due to not having any physical devices", cur_icd_term->scanned_icd->lib_name);
+                if (cur_icd_term->instance) {
+                    cur_icd_term->dispatch.DestroyInstance(cur_icd_term->instance, &(inst->alloc_callbacks));
+                }
+                cur_icd_term->instance = VK_NULL_HANDLE;
+                loader_icd_destroy(inst, cur_icd_term, &(inst->alloc_callbacks));
+                cur_icd_term = NULL;
+                struct loader_scanned_icd *scanned_icd_to_remove = &inst->icd_tramp_list.scanned_list[cur_scanned_icd_index];
+                // Iterate through preloaded ICDs and remove the corresponding driver from that list
+                loader_platform_thread_lock_mutex(&loader_preload_icd_lock);
+                if (NULL != preloaded_icds.scanned_list) {
+                    for (uint32_t i = 0; i < preloaded_icds.count; i++) {
+                        if (strcmp(preloaded_icds.scanned_list[i].lib_name, scanned_icd_to_remove->lib_name) == 0) {
+                            loader_unload_scanned_icd(inst, &preloaded_icds.scanned_list[i]);
+                            break;
+                        }
+                    }
+                }
+                loader_platform_thread_unlock_mutex(&loader_preload_icd_lock);
+
+                loader_unload_scanned_icd(inst, scanned_icd_to_remove);
+            }
+
+            if (NULL == prev_icd_term) {
+                inst->icd_terms = next_icd_term;
+            } else {
+                prev_icd_term->next = next_icd_term;
+            }
+        } else {
+            prev_icd_term = cur_icd_term;
+        }
+        cur_icd_term = next_icd_term;
+    }
+}
 
 VkResult setup_loader_tramp_phys_dev_groups(struct loader_instance *inst, uint32_t group_count,
                                             VkPhysicalDeviceGroupProperties *groups) {
@@ -6696,7 +6765,7 @@ terminator_EnumerateInstanceExtensionProperties(const VkEnumerateInstanceExtensi
         if (VK_SUCCESS != res) {
             goto out;
         }
-        loader_scanned_icd_clear(NULL, &icd_tramp_list);
+        loader_clear_scanned_icd_list(NULL, &icd_tramp_list);
 
         // Append enabled implicit layers.
         res = loader_scan_for_implicit_layers(NULL, &instance_layers, &layer_filters);
index 93346a9d71a088e5fcf9838f0ee4881113de3ed7..6bd7115f4ce5013f9c422dfcc2441ebbd6e41dcd 100644 (file)
@@ -149,8 +149,8 @@ void loader_destroy_pointer_layer_list(const struct loader_instance *inst, struc
 void loader_delete_layer_list_and_properties(const struct loader_instance *inst, struct loader_layer_list *layer_list);
 void loader_remove_layer_in_list(const struct loader_instance *inst, struct loader_layer_list *layer_list,
                                  uint32_t layer_to_remove);
-VkResult loader_scanned_icd_init(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list);
-void loader_scanned_icd_clear(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list);
+VkResult loader_init_scanned_icd_list(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list);
+void loader_clear_scanned_icd_list(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list);
 VkResult loader_icd_scan(const struct loader_instance *inst, struct loader_icd_tramp_list *icd_tramp_list,
                          const VkInstanceCreateInfo *pCreateInfo, bool *skipped_portability_drivers);
 void loader_icd_destroy(struct loader_instance *ptr_inst, struct loader_icd_term *icd_term,
@@ -199,6 +199,7 @@ VkResult loader_validate_device_extensions(struct loader_instance *this_instance
 VkResult setup_loader_tramp_phys_devs(struct loader_instance *inst, uint32_t phys_dev_count, VkPhysicalDevice *phys_devs);
 VkResult setup_loader_tramp_phys_dev_groups(struct loader_instance *inst, uint32_t group_count,
                                             VkPhysicalDeviceGroupProperties *groups);
+void unload_drivers_without_physical_devices(struct loader_instance *inst);
 
 VkStringErrorFlags vk_string_validate(const int max_length, const char *char_array);
 char *loader_get_next_path(char *path);
index 953cbb4cd182f2587c242e1ea7cc8fb46dbfb017..6c2b436826a6bd08d2eb2d16284ddd84433f8e24 100644 (file)
@@ -229,6 +229,8 @@ struct loader_icd_term {
 
     PFN_PhysDevExt phys_dev_ext[MAX_NUM_UNKNOWN_EXTS];
     bool supports_get_dev_prop_2;
+
+    uint32_t physical_device_count;
 };
 
 // Per ICD library structure
index 45d9222191914a1af96c112728ba2ba3a35e90f5..ba35c3f1ff09450c223f4338c8ed3268ca3cb10a 100644 (file)
@@ -777,7 +777,7 @@ out:
             loader_destroy_pointer_layer_list(ptr_instance, &ptr_instance->app_activated_layer_list);
 
             loader_delete_layer_list_and_properties(ptr_instance, &ptr_instance->instance_layer_list);
-            loader_scanned_icd_clear(ptr_instance, &ptr_instance->icd_tramp_list);
+            loader_clear_scanned_icd_list(ptr_instance, &ptr_instance->icd_tramp_list);
             loader_destroy_generic_list(ptr_instance, (struct loader_generic_list *)&ptr_instance->ext_list);
 
             // Free any icd_terms that were created.
@@ -899,11 +899,15 @@ LOADER_EXPORT VKAPI_ATTR VkResult VKAPI_CALL vkEnumeratePhysicalDevices(VkInstan
         if (VK_SUCCESS != update_res) {
             res = update_res;
         }
+
+        // Unloads any drivers that do not expose any physical devices - should save some address space
+        unload_drivers_without_physical_devices(inst);
     }
 
 out:
 
     loader_platform_thread_unlock_mutex(&loader_lock);
+
     return res;
 }
 
index fec5a58d4d93a9d6f2f44963ecf6fe9368df7185..840afae1839bc00de9ed40ba670d70cba7072872 100644 (file)
 #include <chrono>
 #include <iostream>
 #include <vector>
+#include <string>
+#include <iomanip>
+#include <thread>
 
 int main() {
-    uint32_t iterations = 20;
-    std::vector<std::chrono::milliseconds> samples;
-    samples.resize(iterations);
-    for (uint32_t i = 0; i < iterations; i++) {
-        auto t1 = std::chrono::system_clock::now();
-        uint32_t count = 0;
-        vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
-        // vkEnumerateInstanceLayerProperties(&count, nullptr);
-        // vkEnumerateInstanceVersion(&count);
-        auto t2 = std::chrono::system_clock::now();
-        samples[i] = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1);
-    }
-    std::chrono::milliseconds total_time{};
-    for (uint32_t i = 0; i < iterations; i++) {
-        total_time += samples[i];
-    }
-    std::cout << "average time " << total_time.count() / iterations << "ms\n";
-    std::cout << "first call time " << samples[0].count() << "ms\n";
-    std::cout << "second call time " << samples[1].count() << "ms\n";
-    std::cout << "last call time " << samples[iterations - 1].count() << "ms\n";
+    // uint32_t iterations = 20;
+    // std::vector<std::chrono::microseconds> samples;
+    // samples.resize(iterations);
+    // for (uint32_t i = 0; i < iterations; i++) {
+    //     auto t1 = std::chrono::system_clock::now();
+    //     uint32_t count = 0;
+    //     vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
+    //     // vkEnumerateInstanceLayerProperties(&count, nullptr);
+    //     // vkEnumerateInstanceVersion(&count);
+    //     auto t2 = std::chrono::system_clock::now();
+    //     samples[i] = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1);
+    // }
+    // std::chrono::microseconds total_time{};
+    // for (uint32_t i = 0; i < iterations; i++) {
+    //     total_time += samples[i];
+    // }
+    // std::cout << "average time " << total_time.count() / iterations << " (μs)\n";
+    // std::cout << std::setw(10) << "Iteration" << std::setw(12) << " Time (μs)\n";
+    // for (uint32_t i = 0; i < iterations; i++) {
+    //     std::cout << std::setw(10) << std::to_string(i) << std::setw(12) << samples[i].count() << "\n";
+    // }
+
+    uint32_t count = 0;
+    VkInstanceCreateInfo ci{};
+    VkInstance i{};
+    auto res = vkCreateInstance(&ci, nullptr, &i);
+    if (res != VK_SUCCESS) return -1;
+    std::cout << "After called vkCreateInstance\n";
+    do {
+        std::cout << '\n' << "Press a key to continue...";
+    } while (std::cin.get() != '\n');
+    vkDestroyInstance(i, nullptr);
+    std::cout << "After called vkDestroyInstance\n";
+    do {
+        std::cout << '\n' << "Press a key to continue...";
+    } while (std::cin.get() != '\n');
 }
index 52d59d566c90801d359ce58a6ec0d5cba5ee3004..b0bec9182b9e733b8e3f7818c66842f61a757dc4 100644 (file)
@@ -4485,3 +4485,130 @@ TEST(EnumerateAdapterPhysicalDevices, WrongErrorCodes) {
     check_icds();
 }
 #endif  // defined(WIN32)
+
+void try_create_swapchain(DeviceWrapper& dev, VkSurfaceKHR& surface) {
+    PFN_vkCreateSwapchainKHR CreateSwapchainKHR = dev.load("vkCreateSwapchainKHR");
+    PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR = dev.load("vkGetSwapchainImagesKHR");
+    PFN_vkDestroySwapchainKHR DestroySwapchainKHR = dev.load("vkDestroySwapchainKHR");
+    ASSERT_TRUE(nullptr != CreateSwapchainKHR);
+    ASSERT_TRUE(nullptr != GetSwapchainImagesKHR);
+    ASSERT_TRUE(nullptr != DestroySwapchainKHR);
+
+    VkSwapchainKHR swapchain{};
+    VkSwapchainCreateInfoKHR swap_create_info{};
+    swap_create_info.surface = surface;
+
+    ASSERT_EQ(VK_SUCCESS, CreateSwapchainKHR(dev, &swap_create_info, nullptr, &swapchain));
+    uint32_t count = 0;
+    ASSERT_EQ(VK_SUCCESS, GetSwapchainImagesKHR(dev, swapchain, &count, nullptr));
+    ASSERT_GT(count, 0U);
+    std::array<VkImage, 16> images;
+    ASSERT_EQ(VK_SUCCESS, GetSwapchainImagesKHR(dev, swapchain, &count, images.data()));
+    DestroySwapchainKHR(dev, swapchain, nullptr);
+}
+
+TEST(DriverUnloadingFromZeroPhysDevs, InterspersedThroughout) {
+    FrameworkEnvironment env{};
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2))
+        .setup_WSI()
+        .add_physical_device(PhysicalDevice{}.add_extension("VK_KHR_swapchain").finish());
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2))
+        .setup_WSI()
+        .add_physical_device(PhysicalDevice{}.add_extension("VK_KHR_swapchain").finish());
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+
+    InstWrapper inst{env.vulkan_functions};
+    inst.create_info.setup_WSI();
+    inst.CheckCreate();
+
+    auto phys_devs = inst.GetPhysDevs();
+    VkSurfaceKHR surface{};
+    create_surface(inst, surface);
+    for (const auto& phys_dev : phys_devs) {
+        DeviceWrapper dev{inst};
+        dev.create_info.add_extension("VK_KHR_swapchain");
+        dev.CheckCreate(phys_dev);
+
+        try_create_swapchain(dev, surface);
+    }
+    env.vulkan_functions.vkDestroySurfaceKHR(inst.inst, surface, nullptr);
+}
+
+TEST(DriverUnloadingFromZeroPhysDevs, InMiddleOfList) {
+    FrameworkEnvironment env{};
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2))
+        .setup_WSI()
+        .add_physical_device(PhysicalDevice{}.add_extension("VK_KHR_swapchain").finish());
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2))
+        .setup_WSI()
+        .add_physical_device(PhysicalDevice{}.add_extension("VK_KHR_swapchain").finish());
+
+    InstWrapper inst{env.vulkan_functions};
+    inst.create_info.setup_WSI();
+    inst.CheckCreate();
+
+    auto phys_devs = inst.GetPhysDevs();
+    VkSurfaceKHR surface{};
+    create_surface(inst, surface);
+    for (const auto& phys_dev : phys_devs) {
+        DeviceWrapper dev{inst};
+        dev.create_info.add_extension("VK_KHR_swapchain");
+        dev.CheckCreate(phys_dev);
+
+        try_create_swapchain(dev, surface);
+    }
+    env.vulkan_functions.vkDestroySurfaceKHR(inst.inst, surface, nullptr);
+}
+
+TEST(DriverUnloadingFromZeroPhysDevs, AtFrontAndBack) {
+    FrameworkEnvironment env{};
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2))
+        .setup_WSI()
+        .add_physical_device(PhysicalDevice{}.add_extension("VK_KHR_swapchain").finish());
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2))
+        .setup_WSI()
+        .add_physical_device(PhysicalDevice{}.add_extension("VK_KHR_swapchain").finish());
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+
+    InstWrapper inst{env.vulkan_functions};
+    inst.create_info.setup_WSI();
+    inst.CheckCreate();
+
+    auto phys_devs = inst.GetPhysDevs();
+    VkSurfaceKHR surface{};
+    create_surface(inst, surface);
+    for (const auto& phys_dev : phys_devs) {
+        DeviceWrapper dev{inst};
+        dev.create_info.add_extension("VK_KHR_swapchain");
+        dev.CheckCreate(phys_dev);
+
+        try_create_swapchain(dev, surface);
+    }
+    env.vulkan_functions.vkDestroySurfaceKHR(inst.inst, surface, nullptr);
+}
+
+TEST(DriverUnloadingFromZeroPhysDevs, NoPhysicaldevices) {
+    FrameworkEnvironment env{};
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+    env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2)).setup_WSI();
+
+    InstWrapper inst{env.vulkan_functions};
+    inst.create_info.setup_WSI();
+    inst.CheckCreate();
+    // No physical devices == VK_ERROR_INITIALIZATION_FAILED
+    inst.GetPhysDevs(VK_ERROR_INITIALIZATION_FAILED);
+
+    VkSurfaceKHR surface{};
+    create_surface(inst, surface);
+
+    env.vulkan_functions.vkDestroySurfaceKHR(inst.inst, surface, nullptr);
+}