// Copyright (c) Wojciech Figat. All rights reserved. #include "GPUDeviceVulkan.h" #include "GPUAdapterVulkan.h" #include "RenderToolsVulkan.h" #include "Config.h" #include "Engine/Core/Log.h" #include "Engine/Core/Collections/ArrayExtensions.h" #include "Engine/Core/Collections/Sorting.h" #if VULKAN_USE_PERF_SDK #include "Engine/Platform/FileSystem.h" #endif #if GRAPHICS_API_VULKAN #if GPU_ENABLE_DEBUG_LAYER VulkanValidationLevel ValidationLevel = VulkanValidationLevel::ErrorsAndWarningsPerf; #else VulkanValidationLevel ValidationLevel = VulkanValidationLevel::Disabled; #endif #if VULKAN_USE_DEBUG_LAYER #define VULKAN_USE_KHRONOS_STANDARD_VALIDATION 1 // uses VK_LAYER_KHRONOS_validation #define VULKAN_USE_LUNARG_STANDARD_VALIDATION 1 // uses VK_LAYER_LUNARG_standard_validation static const char* GValidationLayers[] = { "VK_LAYER_GOOGLE_threading", "VK_LAYER_LUNARG_parameter_validation", "VK_LAYER_LUNARG_object_tracker", "VK_LAYER_LUNARG_core_validation", nullptr }; #endif static const char* GInstanceExtensions[] = { #if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME) VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, #endif #if VULKAN_USE_VALIDATION_CACHE VK_EXT_VALIDATION_CACHE_EXTENSION_NAME, #endif #if defined(VK_KHR_display) && 0 VK_KHR_DISPLAY_EXTENSION_NAME, #endif #if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, // Required by VK_EXT_host_query_reset (unless using Vulkan 1.1 or newer) #endif nullptr }; static const char* GDeviceExtensions[] = { #if PLATFORM_APPLE_FAMILY && defined(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME) VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME, #endif VK_KHR_SWAPCHAIN_EXTENSION_NAME, #if VK_KHR_maintenance1 VK_KHR_MAINTENANCE1_EXTENSION_NAME, #endif #if VULKAN_USE_VALIDATION_CACHE VK_EXT_VALIDATION_CACHE_EXTENSION_NAME, #endif #if VK_KHR_sampler_mirror_clamp_to_edge VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME, #endif #if VULKAN_USE_TRACY_GPU && VK_EXT_calibrated_timestamps && VK_EXT_host_query_reset VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME, #endif nullptr }; struct LayerExtension { VkLayerProperties Layer; Array Extensions; LayerExtension() { Platform::MemoryClear(&Layer, sizeof(Layer)); } void GetExtensions(Array& result) { for (auto& e : Extensions) { result.AddUnique(StringAnsi(e.extensionName)); } } void GetExtensions(Array& result) { for (auto& e : Extensions) { result.AddUnique(e.extensionName); } } }; static void EnumerateInstanceExtensionProperties(const char* layerName, LayerExtension& outLayer) { VkResult result; do { uint32 count = 0; result = vkEnumerateInstanceExtensionProperties(layerName, &count, nullptr); ASSERT(result >= VK_SUCCESS); if (count > 0) { outLayer.Extensions.Clear(); outLayer.Extensions.AddDefault(count); result = vkEnumerateInstanceExtensionProperties(layerName, &count, outLayer.Extensions.Get()); ASSERT(result >= VK_SUCCESS); } } while (result == VK_INCOMPLETE); } static void EnumerateDeviceExtensionProperties(VkPhysicalDevice device, const char* layerName, LayerExtension& outLayer) { VkResult result; do { uint32 count = 0; result = vkEnumerateDeviceExtensionProperties(device, layerName, &count, nullptr); ASSERT(result >= VK_SUCCESS); if (count > 0) { outLayer.Extensions.Clear(); outLayer.Extensions.AddDefault(count); result = vkEnumerateDeviceExtensionProperties(device, layerName, &count, outLayer.Extensions.Get()); ASSERT(result >= VK_SUCCESS); } } while (result == VK_INCOMPLETE); } static void TrimDuplicates(Array& array) { for (int32 i = array.Count() - 1; i >= 0; i--) { bool found = false; for (int32 j = i - 1; j >= 0; j--) { if (!StringUtils::Compare(array[i], array[j])) { found = true; break; } } if (found) { array.RemoveAt(i); } } } static int FindLayerIndex(const Array& list, const char* layerName) { for (int32 i = 1; i < list.Count(); i++) { if (!StringUtils::Compare(list[i].Layer.layerName, layerName)) { return i; } } return -1; } static bool ContainsLayer(const Array& list, const char* layerName) { return FindLayerIndex(list, layerName) != -1; } static bool FindLayerExtension(const Array& list, const char* extensionName, const char*& foundLayer) { for (int32 extIndex = 0; extIndex < list.Count(); extIndex++) { for (int32 i = 0; i < list[extIndex].Extensions.Count(); i++) { if (!StringUtils::Compare(list[extIndex].Extensions[i].extensionName, extensionName)) { foundLayer = list[extIndex].Layer.layerName; return true; } } } return false; } static bool FindLayerExtension(const Array& list, const char* extensionName) { const char* dummy = nullptr; return FindLayerExtension(list, extensionName, dummy); } static bool ListContains(const Array& list, const char* name) { for (const char* element : list) { if (!StringUtils::Compare(element, name)) return true; } return false; } static bool ListContains(const Array& list, const char* name) { for (const StringAnsi& element : list) { if (element == name) return true; } return false; } #if VULKAN_USE_PERF_SDK namespace { typedef int NVPA_Status; typedef unsigned char NVPA_Bool; struct NVPW_VK_Profiler_GetRequiredInstanceExtensions_Params { size_t structSize; void* pPriv; const char* const* ppInstanceExtensionNames; size_t numInstanceExtensionNames; uint32 apiVersion; NVPA_Bool isOfficiallySupportedVersion; }; struct NVPW_VK_Profiler_GetRequiredDeviceExtensions_Params { size_t structSize; void* pPriv; const char* const* ppDeviceExtensionNames; size_t numDeviceExtensionNames; uint32 apiVersion; NVPA_Bool isOfficiallySupportedVersion; VkInstance instance; VkPhysicalDevice physicalDevice; void* pfnGetInstanceProcAddr; }; struct NVPW_InitializeHost_Params { size_t structSize; void* pPriv; }; typedef NVPA_Status(*PFN_NVPW_VK_Profiler_GetRequiredInstanceExtensions)(NVPW_VK_Profiler_GetRequiredInstanceExtensions_Params* pParams); typedef NVPA_Status(*PFN_NVPW_VK_Profiler_GetRequiredDeviceExtensions)(NVPW_VK_Profiler_GetRequiredDeviceExtensions_Params* pParams); typedef NVPA_Status(*PFN_NVPW_InitializeHost)(NVPW_InitializeHost_Params* pParams); struct NvPerfHostApi { String Path; void* Module = nullptr; PFN_NVPW_VK_Profiler_GetRequiredInstanceExtensions GetRequiredInstanceExtensions = nullptr; PFN_NVPW_VK_Profiler_GetRequiredDeviceExtensions GetRequiredDeviceExtensions = nullptr; }; NvPerfHostApi PerfSDK; } bool GPUDeviceVulkan::UsePerfSDK = false; void GPUDeviceVulkan::PerfSDKInit() { // Check if PerfSDK path has been set and is correct if (Platform::GetEnvironmentVariable(TEXT("NVPERF_SDK_PATH"), PerfSDK.Path) || PerfSDK.Path.IsEmpty() || !FileSystem::DirectoryExists(PerfSDK.Path)) return; // The Nsight Perf SDK host library is named 'nvperf_grfx_host.dll' (see nvperf_host_impl.h). Older/other // packages used 'nvperf.dll'. Try the modern name first, then fall back, so the engine can query the // Range Profiler's REQUIRED Vulkan instance/device extensions at device creation. Without these the // VkDevice is created with only the mini-trace fallback extensions and NVPW_VK_Profiler_Queue_BeginSession // fails with NVPA_STATUS_INVALID_CONTEXT_STATE. const String binDir = PerfSDK.Path / TEXT("NvPerf") / TEXT("bin") / TEXT("x64"); const Char* dllNames[] = { TEXT("nvperf_grfx_host.dll"), TEXT("nvperf.dll") }; String loadedPath; for (const Char* dllName : dllNames) { const String dllPath = binDir / dllName; PerfSDK.Module = Platform::LoadLibrary(dllPath.Get()); if (PerfSDK.Module) { loadedPath = dllPath; break; } } if (!PerfSDK.Module) { LOG(Warning, "Nsight PerfSDK: failed to load NvPerf host library from '{0}' (tried nvperf_grfx_host.dll and nvperf.dll)", binDir); return; } // Initialize host library auto initializeHost = (PFN_NVPW_InitializeHost)Platform::GetProcAddress(PerfSDK.Module, "NVPW_InitializeHost"); if (initializeHost) { NVPW_InitializeHost_Params initParams = {}; initParams.structSize = sizeof(NVPW_InitializeHost_Params); const NVPA_Status initStatus = initializeHost(&initParams); if (initStatus != 0) { LOG(Warning, "Nsight PerfSDK: NVPW_InitializeHost failed (status={0})", (int32)initStatus); return; } } else { LOG(Warning, "Nsight PerfSDK: NVPW_InitializeHost not found in host library"); return; } PerfSDK.GetRequiredInstanceExtensions = (PFN_NVPW_VK_Profiler_GetRequiredInstanceExtensions)Platform::GetProcAddress(PerfSDK.Module, "NVPW_VK_Profiler_GetRequiredInstanceExtensions"); PerfSDK.GetRequiredDeviceExtensions = (PFN_NVPW_VK_Profiler_GetRequiredDeviceExtensions)Platform::GetProcAddress(PerfSDK.Module, "NVPW_VK_Profiler_GetRequiredDeviceExtensions"); LOG(Info, "Nsight PerfSDK: loaded NvPerf host library '{0}' (GetRequiredInstanceExtensions={1}, GetRequiredDeviceExtensions={2}).", loadedPath, PerfSDK.GetRequiredInstanceExtensions ? 1 : 0, PerfSDK.GetRequiredDeviceExtensions ? 1 : 0); // Inform PerfSDK plugin that the Vulkan backend has been setup to use it Platform::SetEnvironmentVariable(TEXT("VULKAN_USE_PERF_SDK"), TEXT("*")); UsePerfSDK = true; // The Vulkan loader reads VK_LOADER_LAYERS_DISABLE / VK_LOADER_DRIVERS_DISABLE exactly once, when it // initializes its global config (the first call into vulkan-1.dll, i.e. volkInitialize). Setting them later // (e.g. during instance-extension enumeration) is silently ignored. NvPerf's Vulkan Range Profiler is not // compatible with object-wrapping layers, so the implicit capture/overlay/Optimus layers must be gone from // the device dispatch chain before the loader caches its config, otherwise BeginSession fails with // NVPA_STATUS_INVALID_CONTEXT_STATE. Likewise the non-NVIDIA ICD must be dropped so there is no multi-ICD // trampoline. Always force these values when PerfSDK is active: a partial user-provided // VK_LOADER_LAYERS_DISABLE (e.g. ~implicit~) still leaves validation/capture wrappers and breaks BeginSession. String existingLayersDisable; Platform::GetEnvironmentVariable(TEXT("VK_LOADER_LAYERS_DISABLE"), existingLayersDisable); if (!existingLayersDisable.IsEmpty() && existingLayersDisable != TEXT("*")) LOG(Warning, "Nsight PerfSDK: overriding VK_LOADER_LAYERS_DISABLE='{0}' with '*' for Range Profiler.", existingLayersDisable); Platform::SetEnvironmentVariable(TEXT("VK_LOADER_LAYERS_DISABLE"), TEXT("*")); #if PLATFORM_WINDOWS _wputenv_s(TEXT("VK_LOADER_LAYERS_DISABLE"), TEXT("*")); #endif LOG(Info, "Nsight PerfSDK: disabled all Vulkan layers before loader init (VK_LOADER_LAYERS_DISABLE=*)."); // Intel's ICD manifest is igvk64.json (does NOT contain "intel" in the filename), so *intel* alone is not enough. const Char* disablePatterns = TEXT("*igvk*,*igd*,*intel*,*Intel*,*amd*,*amdvlk*,*radeon*"); String existingDriversDisable; Platform::GetEnvironmentVariable(TEXT("VK_LOADER_DRIVERS_DISABLE"), existingDriversDisable); if (!existingDriversDisable.IsEmpty() && existingDriversDisable != disablePatterns) LOG(Warning, "Nsight PerfSDK: overriding VK_LOADER_DRIVERS_DISABLE for Range Profiler."); Platform::SetEnvironmentVariable(TEXT("VK_LOADER_DRIVERS_DISABLE"), disablePatterns); #if PLATFORM_WINDOWS _wputenv_s(TEXT("VK_LOADER_DRIVERS_DISABLE"), disablePatterns); #endif LOG(Info, "Nsight PerfSDK: disabled non-NVIDIA Vulkan ICDs before loader init (VK_LOADER_DRIVERS_DISABLE={0}).", String(disablePatterns)); } void GPUDeviceVulkan::PerfSDKInitDeviceInfo(VkDeviceCreateInfo& deviceInfo, const Array& deviceExtensions, VkPhysicalDeviceFeatures2* enabledFeatures2) { // Chains required feature structs onto VkDeviceCreateInfo for enabled PerfSDK extensions const uint32 deviceApiMajorMinor = VK_MAKE_VERSION(VK_VERSION_MAJOR(Adapter->GpuProps.apiVersion), VK_VERSION_MINOR(Adapter->GpuProps.apiVersion), 0); if (deviceApiMajorMinor >= VK_API_VERSION_1_2) { void* next = const_cast(deviceInfo.pNext); PhysicalDeviceFeatures12.bufferDeviceAddress = VK_TRUE; PhysicalDeviceFeatures12.pNext = next; if (enabledFeatures2) { enabledFeatures2->pNext = &PhysicalDeviceFeatures12; deviceInfo.pNext = enabledFeatures2; deviceInfo.pEnabledFeatures = nullptr; } else { deviceInfo.pNext = &PhysicalDeviceFeatures12; } LOG(Info, "Nsight PerfSDK: enabled Vulkan bufferDeviceAddress feature."); return; } if (ListContains(deviceExtensions, "VK_EXT_buffer_device_address")) { static VkPhysicalDeviceBufferDeviceAddressFeaturesEXT bufferDeviceAddressFeatures; void* next = const_cast(deviceInfo.pNext); bufferDeviceAddressFeatures = {}; bufferDeviceAddressFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT; bufferDeviceAddressFeatures.bufferDeviceAddress = VK_TRUE; bufferDeviceAddressFeatures.pNext = next; deviceInfo.pNext = &bufferDeviceAddressFeatures; LOG(Info, "Nsight PerfSDK: enabled VK_EXT_buffer_device_address feature."); } } #endif void GPUDeviceVulkan::GetInstanceLayersAndExtensions(Array& outInstanceExtensions, Array& outInstanceLayers, bool& outDebugUtils, bool useDebugLayer) { VkResult result; outDebugUtils = false; Array globalLayerExtensions; globalLayerExtensions.AddDefault(1); EnumerateInstanceExtensionProperties(nullptr, globalLayerExtensions[0]); Array foundUniqueExtensions; Array foundUniqueLayers; for (int32 i = 0; i < globalLayerExtensions[0].Extensions.Count(); i++) { foundUniqueExtensions.AddUnique(StringAnsi(globalLayerExtensions[0].Extensions[i].extensionName)); } { Array globalLayerProperties; do { uint32 instanceLayerCount = 0; result = vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr); ASSERT(result >= VK_SUCCESS); if (instanceLayerCount > 0) { globalLayerProperties.AddZeroed(instanceLayerCount); result = vkEnumerateInstanceLayerProperties(&instanceLayerCount, &globalLayerProperties[globalLayerProperties.Count() - instanceLayerCount]); ASSERT(result >= VK_SUCCESS); } } while (result == VK_INCOMPLETE); globalLayerExtensions.AddDefault(globalLayerProperties.Count()); for (int32 i = 0; i < globalLayerProperties.Count(); i++) { LayerExtension* layer = &globalLayerExtensions[i + 1]; auto& prop = globalLayerProperties[i]; layer->Layer = prop; EnumerateInstanceExtensionProperties(prop.layerName, *layer); layer->GetExtensions(foundUniqueExtensions); foundUniqueLayers.AddUnique(StringAnsi(prop.layerName)); } } if (foundUniqueLayers.HasItems()) { LOG(Info, "Found instance layers:"); Sorting::QuickSort(foundUniqueLayers); for (const StringAnsi& name : foundUniqueLayers) { LOG(Info, "- {0}", String(name)); } } if (foundUniqueExtensions.HasItems()) { LOG(Info, "Found instance extensions:"); Sorting::QuickSort(foundUniqueExtensions); for (const StringAnsi& name : foundUniqueExtensions) { LOG(Info, "- {0}", String(name)); } } bool vkTrace = false; #if VULKAN_USE_DEBUG_LAYER if (!useDebugLayer) ValidationLevel = VulkanValidationLevel::Disabled; const bool useVkTrace = false; if (useVkTrace && useDebugLayer) { const char* VkTraceName = "VK_LAYER_LUNARG_vktrace"; if (ContainsLayer(globalLayerExtensions, VkTraceName)) { outInstanceLayers.Add(VkTraceName); vkTrace = true; } } #if VULKAN_ENABLE_API_DUMP if (!vkTrace && useDebugLayer) { const char* VkApiDumpName = "VK_LAYER_LUNARG_api_dump"; if (FindLayerInList(globalLayerExtensions, VkApiDumpName)) { outInstanceLayers.Add(VkApiDumpName); } else { LOG(Warning, "Unable to find Vulkan instance layer {0}", String(VkApiDumpName)); } } #endif if (!vkTrace && ValidationLevel != VulkanValidationLevel::Disabled) { bool hasKhronosStandardValidationLayer = false, hasLunargStandardValidationLayer = false; #if VULKAN_USE_KHRONOS_STANDARD_VALIDATION const char* vkLayerKhronosValidation = "VK_LAYER_KHRONOS_validation"; hasKhronosStandardValidationLayer = ContainsLayer(globalLayerExtensions, vkLayerKhronosValidation); if (hasKhronosStandardValidationLayer) { outInstanceLayers.Add(vkLayerKhronosValidation); } else { LOG(Warning, "Unable to find Vulkan instance validation layer {0}", String(vkLayerKhronosValidation)); } #endif #if VULKAN_USE_LUNARG_STANDARD_VALIDATION if (!hasKhronosStandardValidationLayer) { const char* vkLayerLunargStandardValidation = "VK_LAYER_LUNARG_standard_validation"; hasLunargStandardValidationLayer = ContainsLayer(globalLayerExtensions, vkLayerLunargStandardValidation); if (hasLunargStandardValidationLayer) { outInstanceLayers.Add(vkLayerLunargStandardValidation); } else { LOG(Warning, "Unable to find Vulkan instance validation layer {0}", String(vkLayerLunargStandardValidation)); } } #endif if (!hasKhronosStandardValidationLayer && !hasLunargStandardValidationLayer) { for (uint32 i = 0; GValidationLayers[i] != nullptr; i++) { const char* validationLayer = GValidationLayers[i]; const bool validationFound = ContainsLayer(globalLayerExtensions, validationLayer); if (validationFound) { outInstanceLayers.Add(validationLayer); } else { LOG(Warning, "Unable to find Vulkan instance validation layer {0}", String(validationLayer)); } } } } #if VK_EXT_debug_utils if (!vkTrace && ValidationLevel != VulkanValidationLevel::Disabled) { const char* foundDebugUtilsLayer = nullptr; outDebugUtils = FindLayerExtension(globalLayerExtensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME, foundDebugUtilsLayer); if (outDebugUtils && *foundDebugUtilsLayer) { outInstanceLayers.Add(foundDebugUtilsLayer); } } #endif #endif Array platformExtensions; VulkanPlatform::GetInstanceExtensions(platformExtensions, outInstanceLayers); for (const char* extension : platformExtensions) { if (FindLayerExtension(globalLayerExtensions, extension)) { outInstanceExtensions.Add(extension); } } for (int32 i = 0; GInstanceExtensions[i] != nullptr; i++) { if (FindLayerExtension(globalLayerExtensions, GInstanceExtensions[i])) { outInstanceExtensions.Add(GInstanceExtensions[i]); } } #if VK_EXT_debug_utils if (!vkTrace && FindLayerExtension(globalLayerExtensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { outInstanceExtensions.Add(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } #endif if (!vkTrace && ValidationLevel == VulkanValidationLevel::Disabled) { if (FindLayerExtension(globalLayerExtensions, VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) { outInstanceExtensions.Add(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); } } #if VULKAN_USE_PERF_SDK if (UsePerfSDK && PerfSDK.GetRequiredInstanceExtensions) { NVPW_VK_Profiler_GetRequiredInstanceExtensions_Params params = {}; params.structSize = sizeof(NVPW_VK_Profiler_GetRequiredInstanceExtensions_Params); params.apiVersion = VULKAN_API_VERSION; const NVPA_Status status = PerfSDK.GetRequiredInstanceExtensions(¶ms); if (status == 0) { for (size_t i = 0; i < params.numInstanceExtensionNames; i++) { const char* name = params.ppInstanceExtensionNames[i]; const bool available = ListContains(foundUniqueExtensions, name); LOG(Info, "Nsight PerfSDK: Range Profiler requires instance extension '{0}' (available={1}).", String(name), available ? 1 : 0); if (available) outInstanceExtensions.Add(name); } } else LOG(Warning, "Nsight PerfSDK: NVPW_VK_Profiler_GetRequiredInstanceExtensions failed (status={0}); instance will be missing Range Profiler extensions.", (int32)status); } #endif TrimDuplicates(outInstanceLayers); if (outInstanceLayers.HasItems()) { LOG(Info, "Using instance layers:"); for (const char* layer : outInstanceLayers) { LOG(Info, "- {0}", String(layer)); } } else { LOG(Info, "Not using instance layers"); } TrimDuplicates(outInstanceExtensions); if (outInstanceExtensions.HasItems()) { LOG(Info, "Using instance extensions:"); for (const char* extension : outInstanceExtensions) { LOG(Info, "- {0}", String(extension)); } } else { LOG(Info, "Not using instance extensions"); } } void GPUDeviceVulkan::GetDeviceExtensions(VkPhysicalDevice gpu, Array& outDeviceExtensions) { Array deviceLayerExtensions; deviceLayerExtensions.AddDefault(1); { uint32 count = 0; Array properties; VALIDATE_VULKAN_RESULT(vkEnumerateDeviceLayerProperties(gpu, &count, nullptr)); properties.AddZeroed(count); VALIDATE_VULKAN_RESULT(vkEnumerateDeviceLayerProperties(gpu, &count, properties.Get())); ASSERT(count == properties.Count()); for (const VkLayerProperties& property : properties) { deviceLayerExtensions.AddDefault(1); deviceLayerExtensions.Last().Layer = property; } } Array foundUniqueLayers; Array foundUniqueExtensions; for (int32 i = 0; i < deviceLayerExtensions.Count(); i++) { if (i == 0) { EnumerateDeviceExtensionProperties(gpu, nullptr, deviceLayerExtensions[i]); } else { foundUniqueLayers.AddUnique(StringAnsi(deviceLayerExtensions[i].Layer.layerName)); EnumerateDeviceExtensionProperties(gpu, deviceLayerExtensions[i].Layer.layerName, deviceLayerExtensions[i]); } deviceLayerExtensions[i].GetExtensions(foundUniqueExtensions); } if (foundUniqueLayers.HasItems()) { LOG(Info, "Found device layers:"); Sorting::QuickSort(foundUniqueLayers); for (const StringAnsi& name : foundUniqueLayers) { LOG(Info, "- {0}", String(name)); } } if (foundUniqueExtensions.HasItems()) { LOG(Info, "Found device extensions:"); Sorting::QuickSort(foundUniqueExtensions); for (const StringAnsi& name : foundUniqueExtensions) { LOG(Info, "- {0}", String(name)); } } // Add device layers for debugging if (ListContains(foundUniqueLayers, "VK_LAYER_RENDERDOC_Capture")) { IsDebugToolAttached = true; } // Find all extensions Array availableExtensions; availableExtensions.Resize(deviceLayerExtensions[0].Extensions.Count()); for (int32 i = 0; i < deviceLayerExtensions[0].Extensions.Count(); i++) { availableExtensions[i] = deviceLayerExtensions[0].Extensions[i].extensionName; } TrimDuplicates(availableExtensions); // Pick extensions to use Array platformExtensions; VulkanPlatform::GetDeviceExtensions(platformExtensions); for (const char* extension : platformExtensions) { if (ListContains(availableExtensions, extension)) { outDeviceExtensions.Add(extension); break; } } for (uint32 i = 0; i < ARRAY_COUNT(GDeviceExtensions) && GDeviceExtensions[i] != nullptr; i++) { if (ListContains(availableExtensions, GDeviceExtensions[i])) { outDeviceExtensions.Add(GDeviceExtensions[i]); } } #if VULKAN_USE_PERF_SDK if (UsePerfSDK && PerfSDK.GetRequiredDeviceExtensions && Adapter->IsNVIDIA()) { NVPW_VK_Profiler_GetRequiredDeviceExtensions_Params params = {}; params.structSize = sizeof(NVPW_VK_Profiler_GetRequiredDeviceExtensions_Params); params.apiVersion = Adapter->GpuProps.apiVersion; params.instance = Instance; params.physicalDevice = gpu; params.pfnGetInstanceProcAddr = (void*)vkGetInstanceProcAddr; const NVPA_Status status = PerfSDK.GetRequiredDeviceExtensions(¶ms); if (status == 0) { for (size_t i = 0; i < params.numDeviceExtensionNames; i++) { const char* name = params.ppDeviceExtensionNames[i]; const bool available = ListContains(availableExtensions, name); LOG(Info, "Nsight PerfSDK: Range Profiler requires device extension '{0}' (available={1}).", String(name), available ? 1 : 0); if (available) outDeviceExtensions.Add(name); } } else LOG(Warning, "Nsight PerfSDK: NVPW_VK_Profiler_GetRequiredDeviceExtensions failed (status={0}); device will be missing Range Profiler extensions.", (int32)status); } #endif if (outDeviceExtensions.HasItems()) { LOG(Info, "Using device extensions:"); for (const char* extension : outDeviceExtensions) { LOG(Info, "- {0}", String(extension)); } } } void GPUDeviceVulkan::ParseOptionalDeviceExtensions(const Array& deviceExtensions) { Platform::MemoryClear(&OptionalDeviceExtensions, sizeof(OptionalDeviceExtensions)); #if VK_KHR_maintenance1 OptionalDeviceExtensions.HasKHRMaintenance1 = RenderToolsVulkan::HasExtension(deviceExtensions, VK_KHR_MAINTENANCE1_EXTENSION_NAME); #endif #if VK_KHR_maintenance2 OptionalDeviceExtensions.HasKHRMaintenance2 = RenderToolsVulkan::HasExtension(deviceExtensions, VK_KHR_MAINTENANCE2_EXTENSION_NAME); #endif OptionalDeviceExtensions.HasMirrorClampToEdge = RenderToolsVulkan::HasExtension(deviceExtensions, VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME); #if VULKAN_USE_VALIDATION_CACHE OptionalDeviceExtensions.HasEXTValidationCache = RenderToolsVulkan::HasExtension(deviceExtensions, VK_EXT_VALIDATION_CACHE_EXTENSION_NAME); #endif } #endif