// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VulkanLayers.cpp: Vulkan device layers implementation. =============================================================================*/ #include "VulkanRHIPrivate.h" #include "VulkanDevice.h" #include "VulkanExtensions.h" #include "IHeadMountedDisplayModule.h" #include "IHeadMountedDisplayVulkanExtensions.h" #include "Misc/CommandLine.h" #if VULKAN_HAS_DEBUGGING_ENABLED bool GRenderDocFound = false; #endif #if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT #include "IHeadMountedDisplayModule.h" #endif #if VULKAN_HAS_DEBUGGING_ENABLED TAutoConsoleVariable GValidationCvar( TEXT("r.Vulkan.EnableValidation"), VULKAN_VALIDATION_DEFAULT_VALUE, TEXT("0 to disable validation layers\n") TEXT("1 to enable errors\n") TEXT("2 to enable errors & warnings\n") TEXT("3 to enable errors, warnings & performance warnings\n") TEXT("4 to enable errors, warnings, performance & information messages\n") TEXT("5 to enable all messages"), ECVF_ReadOnly | ECVF_RenderThreadSafe ); TAutoConsoleVariable GGPUValidationCvar( TEXT("r.Vulkan.GPUValidation"), 0, TEXT("2 to use enable GPU assisted validation AND extra binding slot when using validation layers\n") TEXT("1 to use enable GPU assisted validation when using validation layers, or\n") TEXT("0 to not use (default)"), ECVF_ReadOnly | ECVF_RenderThreadSafe ); #if VULKAN_ENABLE_DRAW_MARKERS #define RENDERDOC_LAYER_NAME "VK_LAYER_RENDERDOC_Capture" #endif #define KHRONOS_STANDARD_VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation" #endif // VULKAN_HAS_DEBUGGING_ENABLED #define VERIFYVULKANRESULT_INIT(VkFunction) { const VkResult ScopedResult = VkFunction; \ if (ScopedResult == VK_ERROR_INITIALIZATION_FAILED) { \ UE_LOG(LogVulkanRHI, Error, \ TEXT("%s failed\n at %s:%u\nThis typically means Vulkan is not properly set up in your system; try running vulkaninfo from the Vulkan SDK."), \ ANSI_TO_TCHAR(#VkFunction), ANSI_TO_TCHAR(__FILE__), __LINE__); } \ else if (ScopedResult < VK_SUCCESS) { \ VulkanRHI::VerifyVulkanResult(ScopedResult, #VkFunction, __FILE__, __LINE__); }} // Package a layer with its extensions struct FLayerWithExtensions { VkLayerProperties LayerProperties; TArray ExtensionProperties; }; class FVulkanIntanceSetupHelper { public: TArray EnumerateLayerProperties() const { TArray OutLayerProperties; TArray TempLayerProperties; uint32 Count = 0; VERIFYVULKANRESULT_INIT(VulkanRHI::vkEnumerateInstanceLayerProperties(&Count, nullptr)); if (Count > 0) { TempLayerProperties.AddZeroed(Count); VERIFYVULKANRESULT_INIT(VulkanRHI::vkEnumerateInstanceLayerProperties(&Count, TempLayerProperties.GetData())); OutLayerProperties.SetNum(Count); for (uint32 i=0; i < Count; ++i) { OutLayerProperties[i].LayerProperties = TempLayerProperties[i]; OutLayerProperties[i].ExtensionProperties = FVulkanInstanceExtension::GetDriverSupportedInstanceExtensions(TempLayerProperties[i].layerName); UE_LOG(LogVulkanRHI, Display, TEXT("Intance Layer %s provides %d extensions:"), ANSI_TO_TCHAR(TempLayerProperties[i].layerName), (int)OutLayerProperties[i].ExtensionProperties.Num()); for (VkExtensionProperties ep : OutLayerProperties[i].ExtensionProperties) { UE_LOG(LogVulkanRHI, Display, TEXT(" %s"), ANSI_TO_TCHAR(ep.extensionName)); } } } return OutLayerProperties; } TArray GetPlatformLayers() { TArray OutPlatformLayers; FVulkanPlatform::GetInstanceLayers(OutPlatformLayers); return OutPlatformLayers; } void AddDebugLayers(const TArray& LayerProperties, FVulkanInstanceExtensionArray& UEExtensions, TArray& OutLayers); const TCHAR* HelperTypeName = TEXT("instance"); static TArray ExternalLayers; FVulkanDynamicRHI::EActiveDebugLayerExtension ActiveDebugLayerExtension = FVulkanDynamicRHI::EActiveDebugLayerExtension::None; }; TArray FVulkanIntanceSetupHelper::ExternalLayers; class FVulkanDeviceSetupHelper { public: TArray EnumerateLayerProperties() const { TArray OutLayerProperties; TArray TempLayerProperties; uint32 Count = 0; VERIFYVULKANRESULT_INIT(VulkanRHI::vkEnumerateDeviceLayerProperties(Gpu, &Count, nullptr)); if (Count > 0) { TempLayerProperties.AddZeroed(Count); VERIFYVULKANRESULT_INIT(VulkanRHI::vkEnumerateDeviceLayerProperties(Gpu, &Count, TempLayerProperties.GetData())); OutLayerProperties.SetNum(Count); for (uint32 i = 0; i < Count; ++i) { OutLayerProperties[i].LayerProperties = TempLayerProperties[i]; OutLayerProperties[i].ExtensionProperties = FVulkanDeviceExtension::GetDriverSupportedDeviceExtensions(Gpu, TempLayerProperties[i].layerName); UE_LOG(LogVulkanRHI, Display, TEXT("Device Layer %s provides %d extensions:"), ANSI_TO_TCHAR(TempLayerProperties[i].layerName), (int)OutLayerProperties[i].ExtensionProperties.Num()); for (VkExtensionProperties ep : OutLayerProperties[i].ExtensionProperties) { UE_LOG(LogVulkanRHI, Display, TEXT(" %s"), ANSI_TO_TCHAR(ep.extensionName)); } } } return OutLayerProperties; } TArray GetPlatformLayers() { TArray OutPlatformLayers; FVulkanPlatform::GetDeviceLayers(OutPlatformLayers); return OutPlatformLayers; } void AddDebugLayers(const TArray& LayerProperties, FVulkanDeviceExtensionArray& UEExtensions, TArray& OutLayers); const TCHAR* HelperTypeName = TEXT("device"); static TArray ExternalLayers; VkPhysicalDevice Gpu = VK_NULL_HANDLE; bool bSupportsDebugUtilsExt = false; bool bSupportsDebugMarkerExt = false; }; TArray FVulkanDeviceSetupHelper::ExternalLayers; #undef VERIFYVULKANRESULT_INIT // Helper function to find a layer name in a list of LayerProperties static inline int32 FindLayerIndexInList(const char* LayerName, const TArray& LayerProperties) { for (int32 Index = 0; Index < LayerProperties.Num(); ++Index) { if (!FCStringAnsi::Strcmp(LayerProperties[Index].LayerProperties.layerName, LayerName)) { return Index; } } return INDEX_NONE; } // Helper function to add a layer to a list and flag its extensions if it's found template static inline bool AddRequestedLayer(const ANSICHAR* LayerName, const TArray& LayerProperties, TArray>& UEExtensions, TArray& OutLayers) { // Find the layer in the list int32 LayerIndex = FindLayerIndexInList(LayerName, LayerProperties); if (LayerIndex == INDEX_NONE) { return false; } // Add it to the list of used layers OutLayers.Add(LayerName); // Helper function to flag an extension as supported by the driver auto FlagExtensionSupport = [](TArray>& UEExtensions, const ANSICHAR* ExtensionName) { for (TUniquePtr& Extension : UEExtensions) { if (!FCStringAnsi::Strcmp(Extension->GetExtensionName(), ExtensionName)) { Extension->SetSupported(); return true; } } return false; }; // Flag its extensions as usable for (const VkExtensionProperties& ExtensionProperties : LayerProperties[LayerIndex].ExtensionProperties) { FlagExtensionSupport(UEExtensions, ExtensionProperties.extensionName); } return true; } template static TArray SetupLayers(SetupHelperType& VulkanSetupHelper, TArray>& UEExtensions) { TArray OutLayers; // Fetch the list of supported layers TArray LayerProperties = VulkanSetupHelper.EnumerateLayerProperties(); LayerProperties.Sort([](const FLayerWithExtensions& A, const FLayerWithExtensions& B) { return FCStringAnsi::Strcmp(A.LayerProperties.layerName, B.LayerProperties.layerName) < 0; }); UE_LOG(LogVulkanRHI, Display, TEXT("Found %d available %s layers %s"), LayerProperties.Num(), VulkanSetupHelper.HelperTypeName, LayerProperties.Num() ? TEXT(":") : TEXT("!")); for (const FLayerWithExtensions& Prop : LayerProperties) { UE_LOG(LogVulkanRHI, Display, TEXT(" * %s"), ANSI_TO_TCHAR(Prop.LayerProperties.layerName)); } // Check for layers added outside the RHI (eg plugins) for (const ANSICHAR* VulkanBridgeLayer : VulkanSetupHelper.ExternalLayers) { if (!AddRequestedLayer(VulkanBridgeLayer, LayerProperties, UEExtensions, OutLayers)) { UE_LOG(LogVulkanRHI, Warning, TEXT("Unable to find VulkanExternalExtensions %s layer '%s'"), VulkanSetupHelper.HelperTypeName, ANSI_TO_TCHAR(VulkanBridgeLayer)); } } // Check for platform specific layers TArray PlatformLayers = VulkanSetupHelper.GetPlatformLayers(); for (const ANSICHAR* PlatformLayer : PlatformLayers) { if (!AddRequestedLayer(PlatformLayer, LayerProperties, UEExtensions, OutLayers)) { UE_LOG(LogVulkanRHI, Warning, TEXT("Unable to find platform %s layer '%s'"), VulkanSetupHelper.HelperTypeName, ANSI_TO_TCHAR(PlatformLayer)); } } // Check for any requested debug layers VulkanSetupHelper.AddDebugLayers(LayerProperties, UEExtensions, OutLayers); // Clean up the resulting array if (OutLayers.Num() > 0) { auto TrimDuplicates = [](TArray& Array) { for (int32 OuterIndex = Array.Num() - 1; OuterIndex >= 0; --OuterIndex) { bool bFound = false; for (int32 InnerIndex = OuterIndex - 1; InnerIndex >= 0; --InnerIndex) { if (!FCStringAnsi::Strcmp(Array[OuterIndex], Array[InnerIndex])) { bFound = true; break; } } if (bFound) { Array.RemoveAtSwap(OuterIndex, EAllowShrinking::No); } } }; TrimDuplicates(OutLayers); } OutLayers.Sort(); return OutLayers; } void IVulkanDynamicRHI::AddEnabledInstanceExtensionsAndLayers(TArrayView InInstanceExtensions, TArrayView InInstanceLayers) { checkf(!GVulkanRHI, TEXT("AddEnabledInstanceExtensionsAndLayers should be called before the VulkanRHI has been created")); FVulkanInstanceExtension::ExternalExtensions.Append(InInstanceExtensions.GetData(), InInstanceExtensions.Num()); FVulkanIntanceSetupHelper::ExternalLayers.Append(InInstanceLayers.GetData(), InInstanceLayers.Num()); } void IVulkanDynamicRHI::AddEnabledDeviceExtensionsAndLayers(TArrayView InDeviceExtensions, TArrayView InDeviceLayers) { checkf(!GVulkanRHI, TEXT("AddEnabledDeviceExtensionsAndLayers should be called before the VulkanRHI has been created")); FVulkanDeviceExtension::ExternalExtensions.Append(InDeviceExtensions.GetData(), InDeviceExtensions.Num()); FVulkanDeviceSetupHelper::ExternalLayers.Append(InDeviceLayers.GetData(), InDeviceLayers.Num()); } TArray FVulkanDynamicRHI::SetupInstanceLayers(FVulkanInstanceExtensionArray& UEExtensions) { FVulkanIntanceSetupHelper InstanceHelper; TArray OutInstanceLayers = SetupLayers(InstanceHelper, UEExtensions); ActiveDebugLayerExtension = InstanceHelper.ActiveDebugLayerExtension; return OutInstanceLayers; } TArray FVulkanDevice::SetupDeviceLayers(FVulkanDeviceExtensionArray& UEExtensions) { FVulkanDeviceSetupHelper DeviceHelper; DeviceHelper.Gpu = Gpu; DeviceHelper.bSupportsDebugUtilsExt = RHI->SupportsDebugUtilsExt(); TArray OutDeviceLayers = SetupLayers(DeviceHelper, UEExtensions); #if VULKAN_ENABLE_DRAW_MARKERS bUseLegacyDebugMarkerExt = !DeviceHelper.bSupportsDebugUtilsExt && DeviceHelper.bSupportsDebugMarkerExt; #endif return OutDeviceLayers; } void FVulkanDynamicRHI::SetupValidationRequests() { #if VULKAN_HAS_DEBUGGING_ENABLED int32 VulkanValidationOption = GValidationCvar.GetValueOnAnyThread(); // Command line overrides Cvar if (FParse::Param(FCommandLine::Get(), TEXT("vulkandebug"))) { // Match D3D and GL GValidationCvar->Set(2, ECVF_SetByCommandline); } else if (FParse::Value(FCommandLine::Get(), TEXT("vulkanvalidation="), VulkanValidationOption)) { GValidationCvar->Set(VulkanValidationOption, ECVF_SetByCommandline); } if (FParse::Param(FCommandLine::Get(), TEXT("gpuvalidation"))) { if (GValidationCvar->GetInt() < 2) { GValidationCvar->Set(2, ECVF_SetByCommandline); } GGPUValidationCvar->Set(2, ECVF_SetByCommandline); } GRHIGlobals.IsDebugLayerEnabled = (GValidationCvar.GetValueOnAnyThread() > 0); #endif } // Get a list of debugging layers to activate void FVulkanIntanceSetupHelper::AddDebugLayers(const TArray& LayerProperties, FVulkanInstanceExtensionArray& UEExtensions, TArray& OutLayers) { if (FParse::Param(FCommandLine::Get(), TEXT("vktrace"))) { const char* GfxReconstructName = "VK_LAYER_LUNARG_gfxreconstruct"; if (AddRequestedLayer(GfxReconstructName, LayerProperties, UEExtensions, OutLayers)) { ActiveDebugLayerExtension = FVulkanDynamicRHI::EActiveDebugLayerExtension::GfxReconstructLayer; } else { const char* VkTraceName = "VK_LAYER_LUNARG_vktrace"; if (AddRequestedLayer(VkTraceName, LayerProperties, UEExtensions, OutLayers)) { ActiveDebugLayerExtension = FVulkanDynamicRHI::EActiveDebugLayerExtension::VkTraceLayer; } } } const bool bGfxReconstructOrVkTrace = (ActiveDebugLayerExtension != FVulkanDynamicRHI::EActiveDebugLayerExtension::None); #if VULKAN_HAS_DEBUGGING_ENABLED if (FParse::Param(FCommandLine::Get(), TEXT("vulkanapidump"))) { if (bGfxReconstructOrVkTrace) { UE_LOG(LogVulkanRHI, Warning, TEXT("Can't enable api_dump when GfxReconstruct/VkTrace is enabled.")); } else { const char* VkApiDumpName = "VK_LAYER_LUNARG_api_dump"; const bool bApiDumpFound = AddRequestedLayer(VkApiDumpName, LayerProperties, UEExtensions, OutLayers); if (bApiDumpFound) { const FString ApiDumpFileName = FString::Printf(TEXT("%s/vk_apidump.%s.txt"), *FPaths::ProjectLogDir(), *FDateTime::Now().ToString()); FPlatformMisc::SetEnvironmentVar(TEXT("VK_APIDUMP_LOG_FILENAME"), *ApiDumpFileName); FPlatformMisc::SetEnvironmentVar(TEXT("VK_APIDUMP_DETAILED"), TEXT("true")); FPlatformMisc::SetEnvironmentVar(TEXT("VK_APIDUMP_FLUSH"), TEXT("true")); FPlatformMisc::SetEnvironmentVar(TEXT("VK_APIDUMP_OUTPUT_FORMAT"), TEXT("text")); } else { UE_LOG(LogVulkanRHI, Warning, TEXT("Unable to find Vulkan instance layer %s"), ANSI_TO_TCHAR(VkApiDumpName)); } } } // At this point the CVar holds the final value const bool bUseVulkanValidation = GRHIGlobals.IsDebugLayerEnabled; if (!bGfxReconstructOrVkTrace && bUseVulkanValidation) { if (!AddRequestedLayer(KHRONOS_STANDARD_VALIDATION_LAYER_NAME, LayerProperties, UEExtensions, OutLayers)) { #if PLATFORM_WINDOWS || PLATFORM_LINUX //#todo-rco: We don't package DLLs so if this fails it means no DLL was found anywhere, so don't try to load standard validation layers UE_LOG(LogVulkanRHI, Warning, TEXT("Unable to find Vulkan instance validation layer %s; Do you have the Vulkan SDK Installed?"), TEXT(KHRONOS_STANDARD_VALIDATION_LAYER_NAME)); #else UE_LOG(LogVulkanRHI, Warning, TEXT("Unable to find Vulkan instance validation layer %s"), TEXT(KHRONOS_STANDARD_VALIDATION_LAYER_NAME)); #endif } } bool bVulkanEnableDrawMarkers = VULKAN_ENABLE_DRAW_MARKERS; const bool bForceDebugUtils = bVulkanEnableDrawMarkers || FParse::Param(FCommandLine::Get(), TEXT("vulkandebugutils")); if ((bUseVulkanValidation || bForceDebugUtils) && (ActiveDebugLayerExtension == FVulkanDynamicRHI::EActiveDebugLayerExtension::None)) { auto FindLayerContainingExtension = [](const ANSICHAR* ExtensionName, const TArray& LayerProperties) { for (int32 LayerIndex = 0; LayerIndex < LayerProperties.Num(); ++LayerIndex) { for (int32 ExtIndex = 0; ExtIndex < LayerProperties[LayerIndex].ExtensionProperties.Num(); ++ExtIndex) { if (!FCStringAnsi::Strcmp(LayerProperties[LayerIndex].ExtensionProperties[ExtIndex].extensionName, ExtensionName)) { return LayerIndex; } } } return (int32)INDEX_NONE; }; auto ActivateDebuggingExtension = [&](const ANSICHAR* ExtensionName) { const int32 ExtensionIndex = FVulkanInstanceExtension::FindExtension(UEExtensions, ExtensionName); check(ExtensionIndex != INDEX_NONE); // If the extension is supported, activate it and set it as our active debugging extension if (UEExtensions[ExtensionIndex]->IsSupported()) { UEExtensions[ExtensionIndex]->SetActivated(); return true; } return false; }; if (ActivateDebuggingExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) { ActiveDebugLayerExtension = FVulkanDynamicRHI::EActiveDebugLayerExtension::DebugUtilsExtension; } const bool bRequiresValidationFeatures = (GGPUValidationCvar.GetValueOnAnyThread() != 0) || (FParse::Param(FCommandLine::Get(), TEXT("vulkanbestpractices"))) || (FParse::Param(FCommandLine::Get(), TEXT("vulkandebugsync"))); if (bRequiresValidationFeatures) { ActivateDebuggingExtension(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME); } } #endif // VULKAN_HAS_DEBUGGING_ENABLED } // Return a list of debug layers to activate void FVulkanDeviceSetupHelper::AddDebugLayers(const TArray& LayerProperties, FVulkanDeviceExtensionArray& UEExtensions, TArray& OutLayers) { #if VULKAN_HAS_DEBUGGING_ENABLED #if VULKAN_ENABLE_DRAW_MARKERS GRenderDocFound = (FindLayerIndexInList(RENDERDOC_LAYER_NAME, LayerProperties) != INDEX_NONE); #else GRenderDocFound = false; #endif // VULKAN_ENABLE_DRAW_MARKERS #endif // VULKAN_HAS_DEBUGGING_ENABLED #if VULKAN_ENABLE_DRAW_MARKERS // If debug_utils is supported we don't need the legacy extension debug_marker if (!bSupportsDebugUtilsExt) { const int32 ExtensionIndex = FVulkanDeviceExtension::FindExtension(UEExtensions, VK_EXT_DEBUG_MARKER_EXTENSION_NAME); check(ExtensionIndex != INDEX_NONE); // If the extension is supported, activate it and set it as our active debugging extension if (UEExtensions[ExtensionIndex]->IsSupported()) { UEExtensions[ExtensionIndex]->SetActivated(); bSupportsDebugMarkerExt = true; } } #endif // VULKAN_ENABLE_DRAW_MARKERS }