diff --git a/icd/README.md b/icd/README.md index 69fe3d1..0af80c9 100644 --- a/icd/README.md +++ b/icd/README.md @@ -36,6 +36,7 @@ The behavior of the Vulkan SC Emulation ICD can be controlled with the following * `VKSC_EMULATION_DEBUG` works similarly to the loader environment variable `VK_LOADER_DEBUG` and enables different levels of debug output to be generated to `stderr` and/or to API debug callbacks. The variable can be set to a comma-delimited list of log level options (available options: error, warn, info, debug). * `VKSC_EMULATION_RECYCLE_PIPELINE_MEMORY` controls whether the ICD reports support for the `recyclePipelineMemory` Vulkan SC 1.0 device property. If set to `0`, then the ICD will report `recyclePipelineMemory` as `VK_FALSE`, otherwise (by default) as `VK_TRUE`. - * `VKSC_EMULATION_MAX_LOGICAL_DEVICES` can be used to set the maximum number of logical devices that can be createad for a single physical device. The default limit is `65536`. Setting this to `1` can be used to emulate implementations that only support creating a single `VkDevice` in a single `VkInstance`. Note that this limit applies per `VkInstance`, therefore creating more logical devices across multiple `VkInstance` objects is still allowed. + * `VKSC_EMULATION_GLOBAL_MAX_INSTANCES` (Linux only) can be used to set the maximum number of instances that can be created on the system (across all processes). There is no limit by default. Setting this can be used to emulate implementations that only support creating a limited number of `VkInstance` objects. + * `VKSC_EMULATION_MAX_LOGICAL_DEVICES` can be used to set the maximum number of logical devices that can be created for a single physical device. The default limit is `65536`. Setting this to `1` can be used to emulate implementations that only support creating a single `VkDevice` in a single `VkInstance` for a given physical device. Note that this limit applies per `VkInstance`, therefore creating more logical devices across multiple `VkInstance` objects is still allowed. * `VKSC_EMULATION_DISPLAYS` controls the number of `VkDisplayKHR` objects to emulate using Win32/X11 windows on supported platforms and implementations. Setting the variable to `0` disables `VkDisplayKHR` emulation. The default value is `4`. * `VKSC_EMULATION_DISPLAY_CONFIG` can be used to specify a JSON configuration to source for the number and properties of the `VkDisplayKHR` objects to emulate using Win32/X11 windows on supported platforms and implementations. If specified, the JSON configuration overrides the default emulated display parameters and any value specified through the `VKSC_EMULATION_DISPLAYS` environment variable. An example display configuration file can be found in [sample_display_config.json](sample_display_config.json). diff --git a/icd/icd_env_helper.cpp b/icd/icd_env_helper.cpp index 2497a1b..acaa94c 100644 --- a/icd/icd_env_helper.cpp +++ b/icd/icd_env_helper.cpp @@ -20,6 +20,7 @@ namespace icd { EnvironmentHelper::EnvironmentHelper() : log_severity_(ParseLogSeverity()), recycle_pipeline_memory_(ParseRecyclePipelineMemory()), + global_max_instances_(ParseGlobalMaxInstances()), max_logical_devices_(ParseMaxLogicalDevices()), emulated_display_count_(ParseEmulatedDisplayCount()), emulated_display_config_(getenv("VKSC_EMULATION_DISPLAY_CONFIG")), @@ -97,6 +98,14 @@ bool EnvironmentHelper::ParseRecyclePipelineMemory() { return true; } +std::optional EnvironmentHelper::ParseGlobalMaxInstances() { + auto env_var_value = getenv("VKSC_EMULATION_GLOBAL_MAX_INSTANCES"); + if (env_var_value != nullptr) { + return atoi(env_var_value); + } + return {}; +} + uint32_t EnvironmentHelper::ParseMaxLogicalDevices() { // Default is to allow up to 64K logical devices for each physical device uint32_t value = 65536; diff --git a/icd/icd_env_helper.h b/icd/icd_env_helper.h index efed2af..cafb2e0 100644 --- a/icd/icd_env_helper.h +++ b/icd/icd_env_helper.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace icd { @@ -22,6 +23,7 @@ class EnvironmentHelper { VkDebugUtilsMessageSeverityFlagsEXT LogSeverityEnv() const { return log_severity_; } bool RecyclePipelineMemory() const { return recycle_pipeline_memory_; } + const std::optional& GetGlobalMaxInstances() const { return global_max_instances_; } uint32_t GetMaxLogicalDevices() const { return max_logical_devices_; } uint32_t GetEmulatedDisplayCount() const { return emulated_display_count_; } const char* GetEmulatedDisplayConfig() const { return emulated_display_config_; } @@ -32,6 +34,7 @@ class EnvironmentHelper { private: VkDebugUtilsMessageSeverityFlagsEXT ParseLogSeverity(); bool ParseRecyclePipelineMemory(); + std::optional ParseGlobalMaxInstances(); uint32_t ParseMaxLogicalDevices(); uint32_t ParseEmulatedDisplayCount(); const std::unordered_map InitPrivateEnvs(); @@ -40,6 +43,7 @@ class EnvironmentHelper { const VkDebugUtilsMessageSeverityFlagsEXT log_severity_; const bool recycle_pipeline_memory_; + const std::optional global_max_instances_; const uint32_t max_logical_devices_; const uint32_t emulated_display_count_; const char* emulated_display_config_; diff --git a/icd/vksc_global.cpp b/icd/vksc_global.cpp index 970230a..19f38c0 100644 --- a/icd/vksc_global.cpp +++ b/icd/vksc_global.cpp @@ -20,12 +20,20 @@ #include #else #include +#include +#include +#include #endif #include namespace vksc { +#ifndef _WIN32 +static const char* g_global_instance_count_sema_name = "/vksconvk.global_instance_count"; +static sem_t* g_global_instance_count_sema = nullptr; +#endif + Global ICD; Global::Global() : environment_(), logger_(Environment().LogSeverityEnv()), display_manager_(logger_) { @@ -167,6 +175,20 @@ Global::Global() : environment_(), logger_(Environment().LogSeverityEnv()), disp return; } } + +#ifndef _WIN32 + // A named semaphore is used to track global instance count + auto global_max_instances = Environment().GetGlobalMaxInstances(); + if (global_max_instances.has_value()) { + g_global_instance_count_sema = sem_open(g_global_instance_count_sema_name, O_CREAT, 0644, global_max_instances.value()); + if (g_global_instance_count_sema == SEM_FAILED) { + Log().Fatal("VKSC-EMU-GlobalMaxInstances", "Failed to create/open named semaphore to enfore global instance count"); + g_global_instance_count_sema = nullptr; + valid_ = false; + return; + } + } +#endif } Global::~Global() { @@ -177,6 +199,14 @@ Global::~Global() { dlclose(vk_loader_module_); #endif } + +#ifndef _WIN32 + if (g_global_instance_count_sema) { + sem_close(g_global_instance_count_sema); + sem_unlink(g_global_instance_count_sema_name); + g_global_instance_count_sema = nullptr; + } +#endif } VkResult Global::EnumerateInstanceExtensionProperties(const char* pLayerName, uint32_t* pPropertyCount, @@ -203,6 +233,23 @@ VkResult Global::EnumerateInstanceExtensionProperties(const char* pLayerName, ui return result; } +bool Global::AllocateInstance() const { +#ifndef _WIN32 + if (g_global_instance_count_sema) { + return sem_trywait(g_global_instance_count_sema) == 0; + } +#endif + return true; +} + +void Global::FreeInstance() const { +#ifndef _WIN32 + if (g_global_instance_count_sema) { + sem_post(g_global_instance_count_sema); + } +#endif +} + } // namespace vksc extern "C" { @@ -302,6 +349,15 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo* pCre } } + if (result >= VK_SUCCESS) { + if (!vksc::ICD.AllocateInstance()) { + vksc::ICD.Log().Error("VKSC-EMU-GlobalInstanceLimitExceeded", "Global instance limit (%u) exceeded", + vksc::ICD.Environment().GetGlobalMaxInstances().value()); + vksc::Instance::FromHandle(instance)->DestroyInstance(pAllocator); + result = VK_ERROR_TOO_MANY_OBJECTS; + } + } + if (result >= VK_SUCCESS) { *pInstance = instance; } diff --git a/icd/vksc_global.h b/icd/vksc_global.h index 5f47c2c..599fdd0 100644 --- a/icd/vksc_global.h +++ b/icd/vksc_global.h @@ -54,6 +54,9 @@ class Global { const DispatchTable& VkDispatch() const { return vk_dispatch_table_; } PFN_vkGetInstanceProcAddr VkGetProcAddr() const { return vk_get_instance_proc_addr_; } + bool AllocateInstance() const; + void FreeInstance() const; + VkResult EnumerateInstanceExtensionProperties(const char* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties); diff --git a/icd/vksc_instance.cpp b/icd/vksc_instance.cpp index 89eb17c..32f030b 100644 --- a/icd/vksc_instance.cpp +++ b/icd/vksc_instance.cpp @@ -51,6 +51,7 @@ VkResult Instance::SetupInstance(const VkInstanceCreateInfo& create_info) { } void Instance::DestroyInstance(const VkAllocationCallbacks* pAllocator) { + vksc::ICD.FreeInstance(); Destroy(VkDispatch().DestroyInstance, VkHandle(), pAllocator); } diff --git a/icd/vksc_physical_device.cpp b/icd/vksc_physical_device.cpp index 6dd3a25..4201d94 100644 --- a/icd/vksc_physical_device.cpp +++ b/icd/vksc_physical_device.cpp @@ -186,7 +186,7 @@ VkResult PhysicalDevice::CreateDevice(const VkDeviceCreateInfo* pCreateInfo, con // Check device count against the limit only after all other checks completed and we would have otherwise succeeded device // creation (this matches the behavior of other Vulkan SC implementations that have logical device count limits) if (logical_device_count_.fetch_add(1) >= ICD.Environment().GetMaxLogicalDevices()) { - Log().Error("VKSC-EMU-DeviceCountLimitExceeeded", "Logical device limit (%u) exceeded", + Log().Error("VKSC-EMU-DeviceCountLimitExceeded", "Logical device limit (%u) exceeded", ICD.Environment().GetMaxLogicalDevices()); vksc::Device::FromHandle(device)->DestroyDevice(pAllocator); result = VK_ERROR_TOO_MANY_OBJECTS;