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.
// 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
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
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) {
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;
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);
}
// 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);
}
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;
}
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;
}
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++) {
}
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;
}
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) {
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);
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,
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);
PFN_PhysDevExt phys_dev_ext[MAX_NUM_UNKNOWN_EXTS];
bool supports_get_dev_prop_2;
+
+ uint32_t physical_device_count;
};
// Per ICD library structure
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.
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;
}
#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');
}
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);
+}