// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= VulkanRHI.cpp: Vulkan device RHI implementation. =============================================================================*/ #include "VulkanRHIPrivate.h" #include "BuildSettings.h" #include "HardwareInfo.h" #include "VulkanShaderResources.h" #include "VulkanResources.h" #include "VulkanPendingState.h" #include "VulkanContext.h" #include "VulkanBarriers.h" #include "Misc/CommandLine.h" #include "GenericPlatform/GenericPlatformDriver.h" #include "Modules/ModuleManager.h" #include "VulkanPipelineState.h" #include "Misc/FileHelper.h" #include "VulkanLLM.h" #include "Misc/EngineVersion.h" #include "GlobalShader.h" #include "VulkanResourceCollection.h" #include "RHIValidation.h" #include "RHIUtilities.h" #include "ShaderDiagnostics.h" #include "IHeadMountedDisplayModule.h" #include "VulkanRenderpass.h" #include "VulkanTransientResourceAllocator.h" #include "VulkanExtensions.h" #include "VulkanRayTracing.h" #include "VulkanChunkedPipelineCache.h" #if PLATFORM_ANDROID #include "Android/AndroidPlatformMisc.h" #endif // Use Vulkan Profiles to verify feature level support on startup void VulkanProfilePrint(const char* Msg) { UE_LOG(LogVulkanRHI, Log, TEXT(" - %s"), ANSI_TO_TCHAR(Msg)); } #define VP_DEBUG_MESSAGE_CALLBACK VulkanProfilePrint #include "vulkan_profiles_ue.h" #undef VP_DEBUG_MESSAGE_CALLBACK static_assert(sizeof(VkStructureType) == sizeof(int32), "ZeroVulkanStruct() assumes VkStructureType is int32!"); TAtomic GVulkanBufferHandleIdCounter{ 0 }; TAtomic GVulkanBufferViewHandleIdCounter{ 0 }; TAtomic GVulkanImageViewHandleIdCounter{ 0 }; TAtomic GVulkanSamplerHandleIdCounter{ 0 }; TAtomic GVulkanDSetLayoutHandleIdCounter{ 0 }; #if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT #include "IHeadMountedDisplayModule.h" #endif #define LOCTEXT_NAMESPACE "VulkanRHI" /////////////////////////////////////////////////////////////////////////////// TAutoConsoleVariable GRHIThreadCvar( TEXT("r.Vulkan.RHIThread"), 2, TEXT("0 to only use Render Thread\n") TEXT("1 to use ONE RHI Thread\n") TEXT("2 to use multiple RHI Thread\n") ); int32 GVulkanInputAttachmentShaderRead = 0; static FAutoConsoleVariableRef GCVarInputAttachmentShaderRead( TEXT("r.Vulkan.InputAttachmentShaderRead"), GVulkanInputAttachmentShaderRead, TEXT("Whether to use VK_ACCESS_SHADER_READ_BIT an input attachments to workaround rendering issues\n") TEXT("0 use: VK_ACCESS_INPUT_ATTACHMENT_READ_BIT (default)\n") TEXT("1 use: VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_SHADER_READ_BIT\n"), ECVF_ReadOnly ); int32 GVulkanEnableTransientResourceAllocator = 1; static FAutoConsoleVariableRef GCVarEnableTransientResourceAllocator( TEXT("r.Vulkan.EnableTransientResourceAllocator"), GVulkanEnableTransientResourceAllocator, TEXT("Whether to enable the TransientResourceAllocator to reduce memory usage\n") TEXT("0 to disabled (default)\n") TEXT("1 to enable\n"), ECVF_ReadOnly ); static TAutoConsoleVariable CVarAllowVulkanPSOPrecache( TEXT("r.Vulkan.AllowPSOPrecaching"), true, TEXT("true: if r.PSOPrecaching=1 Vulkan RHI will use precaching. (default)\n") TEXT("false: Vulkan RHI will disable precaching (even if r.PSOPrecaching=1)."), ECVF_RenderThreadSafe | ECVF_ReadOnly); // If precaching is active we should not need the file cache. // however, precaching and filecache are compatible with each other, there maybe some scenarios in which both could be used. static TAutoConsoleVariable CVarEnableVulkanPSOFileCacheWhenPrecachingActive( TEXT("r.Vulkan.EnablePSOFileCacheWhenPrecachingActive"), false, TEXT("false: If precaching is available (r.PSOPrecaching=1, r.Vulkan.UseChunkedPSOCache=1) then disable the PSO filecache. (default)\n") TEXT("true: Allow both PSO file cache and precaching."), ECVF_RenderThreadSafe | ECVF_ReadOnly); int32 GVulkanAMDCompatibilityMode = 1; static FAutoConsoleVariableRef GCVarVulkanAMDCompatibilityMode( TEXT("r.Vulkan.AMDCompatibilityMode"), GVulkanAMDCompatibilityMode, TEXT("Used to tweak enabled Vulkan feature set in order to ensure wider compatibility with all AMD GPUs on all platforms. (default:1)"), ECVF_ReadOnly ); extern TAutoConsoleVariable GVulkanRayTracingCVar; // All shader stages supported by VK device - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, FRAGMENT etc uint32 GVulkanDevicePipelineStageBits = 0; DEFINE_LOG_CATEGORY(LogVulkan) // Selects the device to us for the provided instance static VkPhysicalDevice SelectPhysicalDevice(VkInstance InInstance) { VkResult Result; uint32 PhysicalDeviceCount = 0; Result = VulkanRHI::vkEnumeratePhysicalDevices(InInstance, &PhysicalDeviceCount, nullptr); if ((Result != VK_SUCCESS) || (PhysicalDeviceCount == 0)) { UE_LOG(LogVulkanRHI, Log, TEXT("SelectPhysicalDevice could not find a compatible Vulkan device or driver (EnumeratePhysicalDevices returned '%s' and %d devices). ") TEXT("Make sure your video card supports Vulkan and try updating your video driver to a more recent version (proceed with any pending reboots).") , VK_TYPE_TO_STRING(VkResult, Result), PhysicalDeviceCount ); return VK_NULL_HANDLE; } TArray PhysicalDevices; PhysicalDevices.AddZeroed(PhysicalDeviceCount); VERIFYVULKANRESULT(VulkanRHI::vkEnumeratePhysicalDevices(InInstance, &PhysicalDeviceCount, PhysicalDevices.GetData())); checkf(PhysicalDeviceCount >= 1, TEXT("Couldn't enumerate physical devices on second attempt! Make sure your drivers are up to date and that you are not pending a reboot.")); struct FPhysicalDeviceInfo { FPhysicalDeviceInfo() = delete; FPhysicalDeviceInfo(uint32 InOriginalIndex, VkPhysicalDevice InPhysicalDevice) : OriginalIndex(InOriginalIndex) , PhysicalDevice(InPhysicalDevice) { ZeroVulkanStruct(PhysicalDeviceProperties2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2); ZeroVulkanStruct(PhysicalDeviceIDProperties, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES); PhysicalDeviceProperties2.pNext = &PhysicalDeviceIDProperties; VulkanRHI::vkGetPhysicalDeviceProperties2(PhysicalDevice, &PhysicalDeviceProperties2); } uint32 OriginalIndex; VkPhysicalDevice PhysicalDevice; VkPhysicalDeviceProperties2 PhysicalDeviceProperties2; VkPhysicalDeviceIDProperties PhysicalDeviceIDProperties; }; TArray PhysicalDeviceInfos; PhysicalDeviceInfos.Reserve(PhysicalDeviceCount); // Fill the array with each devices properties for (uint32 Index = 0; Index < PhysicalDeviceCount; ++Index) { PhysicalDeviceInfos.Emplace(Index, PhysicalDevices[Index]); } // Allow HMD to override which graphics adapter is chosen, so we pick the adapter where the HMD is connected #if VULKAN_ENABLE_DESKTOP_HMD_SUPPORT if (IHeadMountedDisplayModule::IsAvailable()) { static_assert(sizeof(uint64) == VK_LUID_SIZE); const uint64 HmdGraphicsAdapterLuid = IHeadMountedDisplayModule::Get().GetGraphicsAdapterLuid(); for (int32 Index = 0; Index < PhysicalDeviceInfos.Num(); ++Index) { if (FMemory::Memcmp(&HmdGraphicsAdapterLuid, &PhysicalDeviceInfos[Index].PhysicalDeviceIDProperties.deviceLUID, VK_LUID_SIZE_KHR) == 0) { UE_LOG(LogVulkanRHI, Log, TEXT("HMD device at index %d of %u being used as default..."), Index, PhysicalDeviceCount); return PhysicalDeviceInfos[Index].PhysicalDevice; } } } #endif // Use the device as forced by CVar or CommandLine arg auto* CVarGraphicsAdapter = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GraphicsAdapter")); int32 ExplicitAdapterValue = CVarGraphicsAdapter ? CVarGraphicsAdapter->GetValueOnAnyThread() : -1; const bool bUsingCmdLine = FParse::Value(FCommandLine::Get(), TEXT("graphicsadapter="), ExplicitAdapterValue); const TCHAR* GraphicsAdapterOriginTxt = bUsingCmdLine ? TEXT("command line") : TEXT("'r.GraphicsAdapter'"); if (ExplicitAdapterValue >= 0) // Use adapter at the specified index { if (ExplicitAdapterValue >= PhysicalDeviceInfos.Num()) { UE_LOG(LogVulkanRHI, Warning, TEXT("Tried to use graphics adapter at index %d as specified by %s, but only %d Adapter(s) found. Falling back to first device..."), ExplicitAdapterValue, GraphicsAdapterOriginTxt, PhysicalDeviceInfos.Num()); ExplicitAdapterValue = 0; } UE_LOG(LogVulkanRHI, Log, TEXT("Using device at index %d of %u as specfified by %s..."), ExplicitAdapterValue, PhysicalDeviceCount, GraphicsAdapterOriginTxt); return PhysicalDeviceInfos[ExplicitAdapterValue].PhysicalDevice; } else if (ExplicitAdapterValue == -2) // Take the first one that fulfills the criteria { UE_LOG(LogVulkanRHI, Log, TEXT("Using first device (of %u) without any sorting as specfified by %s..."), PhysicalDeviceCount, GraphicsAdapterOriginTxt); return PhysicalDeviceInfos[0].PhysicalDevice; } else if (ExplicitAdapterValue == -1) // Favour non integrated because there are usually faster { // Reoreder the list to place discrete adapters first PhysicalDeviceInfos.Sort([](const FPhysicalDeviceInfo& Lhs, const FPhysicalDeviceInfo& Rhs) { // For devices of the same type, jsut keep the original order if (Lhs.PhysicalDeviceProperties2.properties.deviceType == Rhs.PhysicalDeviceProperties2.properties.deviceType) { return Lhs.OriginalIndex < Rhs.OriginalIndex; } // Prefer discrete GPUs first, then integrated, then CPU return (Lhs.PhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) || (Rhs.PhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU); }); } // If a preferred vendor is specified, return the first device from that vendor const EGpuVendorId PreferredVendor = RHIGetPreferredAdapterVendor(); if (PreferredVendor != EGpuVendorId::Unknown) { // Check for preferred for (int32 Index = 0; Index < PhysicalDeviceInfos.Num(); ++Index) { if (RHIConvertToGpuVendorId(PhysicalDeviceInfos[Index].PhysicalDeviceProperties2.properties.vendorID) == PreferredVendor) { UE_LOG(LogVulkanRHI, Log, TEXT("Using preferred vendor device at index %d of %u..."), Index, PhysicalDeviceCount); return PhysicalDeviceInfos[Index].PhysicalDevice; } } } // Skip all CPU devices if they aren't permitted const bool bAllowCPUDevices = FParse::Param(FCommandLine::Get(), TEXT("AllowCPUDevices")); for (int32 Index = 0; Index < PhysicalDeviceInfos.Num(); ++Index) { if (!bAllowCPUDevices && (PhysicalDeviceInfos[Index].PhysicalDeviceProperties2.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU)) { continue; } return PhysicalDeviceInfos[Index].PhysicalDevice; } UE_LOG(LogVulkanRHI, Warning, TEXT("None of the %u devices meet all the criteria!"), PhysicalDeviceCount); return VK_NULL_HANDLE; } static uint32 GetVulkanApiVersionForFeatureLevel(ERHIFeatureLevel::Type FeatureLevel, bool bRaytracing) { const FString ProfileName = FVulkanPlatform::GetVulkanProfileNameForFeatureLevel(FeatureLevel, bRaytracing); VpProfileProperties ProfileProperties; FMemory::Memzero(ProfileProperties); FCStringAnsi::Strncpy(ProfileProperties.profileName, TCHAR_TO_ANSI(*ProfileName), VP_MAX_PROFILE_NAME_SIZE); const uint32 minApiVersion = vpGetProfileAPIVersion(&ProfileProperties); if (minApiVersion) { return minApiVersion; } UE_LOG(LogVulkanRHI, Log, TEXT("Using default apiVersion for platform...")); return UE_VK_API_VERSION; } // Returns the API version for the provided feautre level, returns 0 if not supported static bool CheckVulkanProfile(ERHIFeatureLevel::Type FeatureLevel, bool bRaytracing) { const FString ProfileName = FVulkanPlatform::GetVulkanProfileNameForFeatureLevel(FeatureLevel, bRaytracing); if (!FVulkanGenericPlatform::SupportsProfileChecks()) { UE_LOG(LogVulkanRHI, Log, TEXT("Skipping Vulkan Profile check for %s:"), *ProfileName); return true; } UE_LOG(LogVulkanRHI, Log, TEXT("Starting Vulkan Profile check for %s:"), *ProfileName); ON_SCOPE_EXIT { UE_LOG(LogVulkanRHI, Log, TEXT("Vulkan Profile check complete.")); }; VpProfileProperties ProfileProperties; FMemory::Memzero(ProfileProperties); FCStringAnsi::Strncpy(ProfileProperties.profileName, TCHAR_TO_ANSI(*ProfileName), VP_MAX_PROFILE_NAME_SIZE); VkBool32 bInstanceSupported = VK_FALSE; VkResult InstanceResult = vpGetInstanceProfileSupport(nullptr, &ProfileProperties, &bInstanceSupported); // :todo-jn: no VERIFYVULKANRESULT, this can fail and it's fine if ((InstanceResult == VK_SUCCESS) && bInstanceSupported) { VkInstanceCreateInfo InstanceCreateInfo; ZeroVulkanStruct(InstanceCreateInfo, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO); VpInstanceCreateInfo ProfileInstanceCreateInfo; FMemory::Memzero(ProfileInstanceCreateInfo); ProfileInstanceCreateInfo.enabledFullProfileCount = 1; ProfileInstanceCreateInfo.pEnabledFullProfiles = &ProfileProperties; ProfileInstanceCreateInfo.pCreateInfo = &InstanceCreateInfo; VkInstance TempInstance = VK_NULL_HANDLE; VERIFYVULKANRESULT(vpCreateInstance(&ProfileInstanceCreateInfo, VULKAN_CPU_ALLOCATOR, &TempInstance)); // Use FVulkanGenericPlatform on purpose here, we only want basic common functionality (no platform specific stuff) FVulkanGenericPlatform::LoadVulkanInstanceFunctions(TempInstance); ON_SCOPE_EXIT { // Keep nothing around from the temporary instance we created if (TempInstance) { VulkanRHI::vkDestroyInstance(TempInstance, VULKAN_CPU_ALLOCATOR); TempInstance = VK_NULL_HANDLE; FVulkanPlatform::ClearVulkanInstanceFunctions(); } }; // Pick the device we would use on this instance const VkPhysicalDevice PhysicalDevice = SelectPhysicalDevice(TempInstance); if (PhysicalDevice) { VkBool32 bDeviceSupported = VK_FALSE; VERIFYVULKANRESULT(vpGetPhysicalDeviceProfileSupport(TempInstance, PhysicalDevice, &ProfileProperties, &bDeviceSupported)); if (bDeviceSupported) { return true; } } } return false; } void FVulkanDynamicRHIModule::StartupModule() { #if VULKAN_USE_LLM LLM(VulkanLLM::Initialize()); #endif } bool FVulkanDynamicRHIModule::IsSupported() { if (FVulkanPlatform::IsSupported()) { return FVulkanPlatform::LoadVulkanLibrary(); } return false; } bool FVulkanDynamicRHIModule::IsSupported(ERHIFeatureLevel::Type FeatureLevel) { if (IsSupported()) { if (FeatureLevel == ERHIFeatureLevel::ES3_1) { return !GIsEditor; } else if (!FVulkanPlatform::SupportsProfileChecks()) { return true; } else { return CheckVulkanProfile(FeatureLevel, false); } } return false; } FDynamicRHI* FVulkanDynamicRHIModule::CreateRHI(ERHIFeatureLevel::Type InRequestedFeatureLevel) { GMaxRHIFeatureLevel = FVulkanPlatform::GetFeatureLevel(InRequestedFeatureLevel); checkf(GMaxRHIFeatureLevel != ERHIFeatureLevel::Num, TEXT("Invalid feature level requested!")); EShaderPlatform ShaderPlatformForFeatureLevel[ERHIFeatureLevel::Num]; FVulkanPlatform::SetupFeatureLevels(ShaderPlatformForFeatureLevel); GMaxRHIShaderPlatform = ShaderPlatformForFeatureLevel[GMaxRHIFeatureLevel]; checkf(GMaxRHIShaderPlatform != SP_NumPlatforms, TEXT("Requested feature level [%s] mapped to unsupported shader platform!"), *LexToString(InRequestedFeatureLevel)); UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan RHI ShaderPlatform for %s: %s."), *LexToString(InRequestedFeatureLevel), *LexToString(GMaxRHIShaderPlatform, false)); GVulkanRHI = new FVulkanDynamicRHI(); FDynamicRHI* FinalRHI = GVulkanRHI; #if ENABLE_RHI_VALIDATION if (FParse::Param(FCommandLine::Get(), TEXT("RHIValidation"))) { FinalRHI = new FValidationRHI(FinalRHI); } #endif for (int32 Index = 0; Index < ERHIFeatureLevel::Num; ++Index) { if (ShaderPlatformForFeatureLevel[Index] != SP_NumPlatforms) { check(GMaxTextureSamplers >= (int32)FDataDrivenShaderPlatformInfo::GetMaxSamplers(ShaderPlatformForFeatureLevel[Index])); if (GMaxTextureSamplers < (int32)FDataDrivenShaderPlatformInfo::GetMaxSamplers(ShaderPlatformForFeatureLevel[Index])) { UE_LOG(LogVulkanRHI, Error, TEXT("Shader platform requires at least: %d samplers, device supports: %d."), FDataDrivenShaderPlatformInfo::GetMaxSamplers(ShaderPlatformForFeatureLevel[Index]), GMaxTextureSamplers); } } } return FinalRHI; } IMPLEMENT_MODULE(FVulkanDynamicRHIModule, VulkanRHI); FVulkanCommandListContextImmediate::FVulkanCommandListContextImmediate(FVulkanDevice& InDevice) : FVulkanCommandListContext(InDevice, ERHIPipeline::Graphics, nullptr) { } FVulkanDynamicRHI::FVulkanDynamicRHI() : Instance(VK_NULL_HANDLE) , Device(nullptr) , DrawingViewport(nullptr) { // This should be called once at the start check(IsInGameThread()); check(!GIsThreadedRendering); GPoolSizeVRAMPercentage = 0; GTexturePoolSize = 0; GRHISupportsMultithreading = true; GRHISupportsMultithreadedResources = true; GRHITransitionPrivateData_SizeInBytes = sizeof(FVulkanTransitionData); GRHITransitionPrivateData_AlignInBytes = alignof(FVulkanTransitionData); GConfig->GetInt(TEXT("TextureStreaming"), TEXT("PoolSizeVRAMPercentage"), GPoolSizeVRAMPercentage, GEngineIni); GRHIGlobals.SupportsBarycentricsSemantic = true; GRHISupportsPSOPrecaching = CVarAllowVulkanPSOPrecache.GetValueOnAnyThread(); GRHISupportsPipelineFileCache = !GRHISupportsPSOPrecaching || CVarEnableVulkanPSOFileCacheWhenPrecachingActive.GetValueOnAnyThread(); UE_LOG(LogVulkanRHI, Log, TEXT("Vulkan PSO Precaching = %d, PipelineFileCache = %d"), GRHISupportsPSOPrecaching, GRHISupportsPipelineFileCache); // Copy source requires its own image layout. EnumRemoveFlags(GRHIMergeableAccessMask, ERHIAccess::CopySrc); // Setup the validation requests ready before we load dlls SetupValidationRequests(); UE_LOG(LogVulkanRHI, Display, TEXT("Built with Vulkan header version %u.%u.%u"), VK_API_VERSION_MAJOR(VK_HEADER_VERSION_COMPLETE), VK_API_VERSION_MINOR(VK_HEADER_VERSION_COMPLETE), VK_API_VERSION_PATCH(VK_HEADER_VERSION_COMPLETE)); CreateInstance(); SelectDevice(); } FVulkanDynamicRHI::~FVulkanDynamicRHI() = default; void FVulkanDynamicRHI::Init() { InitInstance(); bIsStandaloneStereoDevice = IHeadMountedDisplayModule::IsAvailable() && IHeadMountedDisplayModule::Get().IsStandaloneStereoOnlyDevice(); static const auto CVarStreamingTexturePoolSize = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Streaming.PoolSize")); int32 StreamingPoolSizeValue = CVarStreamingTexturePoolSize->GetValueOnAnyThread(); if (GPoolSizeVRAMPercentage > 0) { const uint64 TotalGPUMemory = Device->GetDeviceMemoryManager().GetTotalMemory(true); float PoolSize = float(GPoolSizeVRAMPercentage) * 0.01f * float(TotalGPUMemory); // Truncate GTexturePoolSize to MB (but still counted in bytes) GTexturePoolSize = int64(FGenericPlatformMath::TruncToFloat(PoolSize / 1024.0f / 1024.0f)) * 1024 * 1024; UE_LOG(LogRHI, Log, TEXT("Texture pool is %llu MB (%d%% of %llu MB)"), GTexturePoolSize / 1024 / 1024, GPoolSizeVRAMPercentage, TotalGPUMemory / 1024 / 1024); } else if (StreamingPoolSizeValue > 0) { GTexturePoolSize = (int64)StreamingPoolSizeValue * 1024 * 1024; const uint64 TotalGPUMemory = Device->GetDeviceMemoryManager().GetTotalMemory(true); UE_LOG(LogRHI,Log,TEXT("Texture pool is %llu MB (of %llu MB total graphics mem)"), GTexturePoolSize / 1024 / 1024, TotalGPUMemory / 1024 / 1024); } } void FVulkanDynamicRHI::PostInit() { if (GRHISupportsRayTracing) { Device->InitializeRayTracing(); } } void FVulkanDynamicRHI::Shutdown() { if (FParse::Param(FCommandLine::Get(), TEXT("savevulkanpsocacheonexit"))) { SavePipelineCache(); } check(IsInGameThread() && IsInRenderingThread()); check(Device); Device->PrepareForDestroy(); EmptyCachedBoundShaderStates(); FVulkanVertexDeclaration::EmptyCache(); if (GIsRHIInitialized) { // Reset the RHI initialized flag. GIsRHIInitialized = false; FVulkanPlatform::OverridePlatformHandlers(false); GRHINeedsExtraDeletionLatency = false; check(!GIsCriticalError); // Ask all initialized FRenderResources to release their RHI resources. FRenderResource::ReleaseRHIForAllResources(); { for (auto& Pair : Device->SamplerMap) { FVulkanSamplerState* SamplerState = (FVulkanSamplerState*)Pair.Value.GetReference(); VulkanRHI::vkDestroySampler(Device->GetInstanceHandle(), SamplerState->Sampler, VULKAN_CPU_ALLOCATOR); } Device->SamplerMap.Empty(); } Device->CleanUpRayTracing(); // Flush all pending deletes before destroying the device. FRHICommandListImmediate::Get().ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources); ShutdownSubmissionPipe(); } Device->Destroy(); delete Device; Device = nullptr; // Release the early HMD interface used to query extra extensions - if any was used HMDVulkanExtensions = nullptr; #if VULKAN_HAS_DEBUGGING_ENABLED RemoveDebugLayerCallback(); #endif VulkanRHI::vkDestroyInstance(Instance, VULKAN_CPU_ALLOCATOR); IConsoleManager::Get().UnregisterConsoleObject(SavePipelineCacheCmd); IConsoleManager::Get().UnregisterConsoleObject(RebuildPipelineCacheCmd); #if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT IConsoleManager::Get().UnregisterConsoleObject(DumpMemoryCmd); IConsoleManager::Get().UnregisterConsoleObject(DumpMemoryFullCmd); IConsoleManager::Get().UnregisterConsoleObject(DumpStagingMemoryCmd); IConsoleManager::Get().UnregisterConsoleObject(DumpLRUCmd); IConsoleManager::Get().UnregisterConsoleObject(TrimLRUCmd); #endif FVulkanPlatform::FreeVulkanLibrary(); #if VULKAN_ENABLE_DUMP_LAYER VulkanRHI::FlushDebugWrapperLog(); #endif } void FVulkanDynamicRHI::CreateInstance() { // Engine registration can be disabled via console var. Also disable automatically if ShaderDevelopmentMode is on. auto* CVarDisableEngineAndAppRegistration = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DisableEngineAndAppRegistration")); bool bDisableEngineRegistration = (CVarDisableEngineAndAppRegistration && CVarDisableEngineAndAppRegistration->GetValueOnAnyThread() != 0) || IsShaderDevelopmentModeEnabled(); // Use the API version stored in the profile ApiVersion = GetVulkanApiVersionForFeatureLevel(GMaxRHIFeatureLevel, false); // Run a profile check to see if this device can support our raytacing requirements since it might change the required API version of the instance if (FVulkanPlatform::SupportsProfileChecks() && GVulkanRayTracingCVar.GetValueOnAnyThread()) { const bool bRayTracingAllowedOnCurrentShaderPlatform = (GMaxRHIShaderPlatform == SP_VULKAN_SM6 || IsVulkanMobileSM5Platform(GMaxRHIShaderPlatform)); if (CheckVulkanProfile(GMaxRHIFeatureLevel, true) && bRayTracingAllowedOnCurrentShaderPlatform) { // Raytracing is supported, update the required API version ApiVersion = GetVulkanApiVersionForFeatureLevel(GMaxRHIFeatureLevel, true); } else { // Raytracing is not supported, disable it completely instead of only loading parts of it GVulkanRayTracingCVar->Set(0, ECVF_SetByCode); if (!bRayTracingAllowedOnCurrentShaderPlatform) { UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan RayTracing disabled because SM6 shader platform is required.")); } else { UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan RayTracing disabled because of failed profile check.")); } } } UE_LOG(LogVulkanRHI, Log, TEXT("Using API Version %u.%u."), VK_API_VERSION_MAJOR(ApiVersion), VK_API_VERSION_MINOR(ApiVersion)); // EngineName will be of the form "UnrealEngine4.21", with the minor version ("21" in this example) // updated with every quarterly release FString EngineName = FApp::GetEpicProductIdentifier() + FEngineVersion::Current().ToString(EVersionComponent::Minor); FTCHARToUTF8 EngineNameConverter(*EngineName); FTCHARToUTF8 ProjectNameConverter(FApp::GetProjectName()); VkApplicationInfo AppInfo; ZeroVulkanStruct(AppInfo, VK_STRUCTURE_TYPE_APPLICATION_INFO); AppInfo.pApplicationName = bDisableEngineRegistration ? nullptr : ProjectNameConverter.Get(); AppInfo.applicationVersion = static_cast(BuildSettings::GetCurrentChangelist()) | (BuildSettings::IsLicenseeVersion() ? 0x80000000 : 0); AppInfo.pEngineName = bDisableEngineRegistration ? nullptr : EngineNameConverter.Get(); AppInfo.engineVersion = FEngineVersion::Current().GetMinor(); AppInfo.apiVersion = ApiVersion; VkInstanceCreateInfo InstInfo; ZeroVulkanStruct(InstInfo, VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO); InstInfo.pApplicationInfo = &AppInfo; FVulkanInstanceExtensionArray UEInstanceExtensions = FVulkanInstanceExtension::GetUESupportedInstanceExtensions(ApiVersion); InstanceLayers = SetupInstanceLayers(UEInstanceExtensions); for (TUniquePtr& Extension : UEInstanceExtensions) { if (Extension->InUse()) { InstanceExtensions.Add(Extension->GetExtensionName()); Extension->PreCreateInstance(InstInfo, OptionalInstanceExtensions); } } InstInfo.enabledExtensionCount = InstanceExtensions.Num(); InstInfo.ppEnabledExtensionNames = InstInfo.enabledExtensionCount > 0 ? (const ANSICHAR* const*)InstanceExtensions.GetData() : nullptr; InstInfo.enabledLayerCount = InstanceLayers.Num(); InstInfo.ppEnabledLayerNames = InstInfo.enabledLayerCount > 0 ? InstanceLayers.GetData() : nullptr; VkResult Result = VulkanRHI::vkCreateInstance(&InstInfo, VULKAN_CPU_ALLOCATOR, &Instance); if (Result == VK_ERROR_LAYER_NOT_PRESENT) { UE_LOG(LogVulkanRHI, Warning, TEXT("Vulkan instance creation returned an error with the requested layers (%d):"), InstanceLayers.Num()); for (const ANSICHAR* AnsiLayerName : InstanceLayers) { const FString LayerStr = ANSI_TO_TCHAR(AnsiLayerName); UE_LOG(LogVulkanRHI, Warning, TEXT("- %s"), *LayerStr); } const EAppReturnType::Type MsgBoxResult = FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, TEXT( "ERROR: Vulkan driver couldn't load one of the requested layers (see log for details).\n\n" "Retry without layers?"), TEXT("Incompatible Vulkan layer found!")); if (MsgBoxResult == EAppReturnType::Yes) { InstInfo.enabledLayerCount = 0; Result = VulkanRHI::vkCreateInstance(&InstInfo, VULKAN_CPU_ALLOCATOR, &Instance); } else { FPlatformMisc::RequestExitWithStatus(true, 1); // unreachable return; } } FVulkanPlatform::NotifyFoundInstanceLayersAndExtensions(InstanceLayers, InstanceExtensions); if (Result == VK_ERROR_INCOMPATIBLE_DRIVER) { FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT( "Cannot find a compatible Vulkan driver (ICD).\n\nPlease look at the Getting Started guide for " "additional information."), TEXT("Incompatible Vulkan driver found!")); FPlatformMisc::RequestExitWithStatus(true, 1); // unreachable return; } else if(Result == VK_ERROR_EXTENSION_NOT_PRESENT) { // Check for missing extensions FString MissingExtensions; uint32_t PropertyCount; VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &PropertyCount, nullptr); TArray Properties; Properties.SetNum(PropertyCount); VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &PropertyCount, Properties.GetData()); for (const ANSICHAR* Extension : InstanceExtensions) { bool bExtensionFound = false; for (uint32_t PropertyIndex = 0; PropertyIndex < PropertyCount; PropertyIndex++) { const char* PropertyExtensionName = Properties[PropertyIndex].extensionName; if (!FCStringAnsi::Strcmp(PropertyExtensionName, Extension)) { bExtensionFound = true; break; } } if (!bExtensionFound) { FString ExtensionStr = ANSI_TO_TCHAR(Extension); UE_LOG(LogVulkanRHI, Error, TEXT("Missing required Vulkan extension: %s"), *ExtensionStr); MissingExtensions += ExtensionStr + TEXT("\n"); } } FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *FString::Printf(TEXT( "Vulkan driver doesn't contain specified extensions:\n%s;\n\ make sure your layers path is set appropriately."), *MissingExtensions), TEXT("Incomplete Vulkan driver found!")); } else if (Result != VK_SUCCESS) { FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT( "Vulkan failed to create instance.\n\nDo you have a compatible Vulkan " "driver (ICD) installed?\nPlease look at " "the Getting Started guide for additional information."), TEXT("No Vulkan driver found!")); FPlatformMisc::RequestExitWithStatus(true, 1); // unreachable return; } VERIFYVULKANRESULT(Result); if (!FVulkanPlatform::LoadVulkanInstanceFunctions(Instance)) { FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT( "Failed to find all required Vulkan entry points! Try updating your driver."), TEXT("No Vulkan entry points found!")); } #if VULKAN_HAS_DEBUGGING_ENABLED SetupDebugLayerCallback(); #endif } void FVulkanDynamicRHI::SelectDevice() { VkPhysicalDevice PhysicalDevice = SelectPhysicalDevice(Instance); if (PhysicalDevice == VK_NULL_HANDLE) { // Shouldn't be possible if profile checks passed prior to this FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, TEXT("Vulkan failed to select physical device after passing profile checks."), TEXT("No Vulkan driver found!")); FPlatformMisc::RequestExitWithStatus(true, 1); return; } UE_LOG(LogVulkanRHI, Log, TEXT("Creating Vulkan Device using VkPhysicalDevice 0x%x."), reinterpret_cast(PhysicalDevice)); Device = new FVulkanDevice(this, PhysicalDevice); const VkPhysicalDeviceProperties& Props = Device->GetDeviceProperties(); bool bUseVendorIdAsIs = true; if (Props.vendorID > 0xffff) { bUseVendorIdAsIs = false; VkVendorId VendorId = (VkVendorId)Props.vendorID; switch (VendorId) { case VK_VENDOR_ID_VIV: GRHIVendorId = (uint32)EGpuVendorId::Vivante; break; case VK_VENDOR_ID_VSI: GRHIVendorId = (uint32)EGpuVendorId::VeriSilicon; break; case VK_VENDOR_ID_KAZAN: GRHIVendorId = (uint32)EGpuVendorId::Kazan; break; case VK_VENDOR_ID_CODEPLAY: GRHIVendorId = (uint32)EGpuVendorId::Codeplay; break; case VK_VENDOR_ID_MESA: GRHIVendorId = (uint32)EGpuVendorId::Mesa; break; default: // Unhandled case UE_LOG(LogVulkanRHI, Warning, TEXT("Unhandled VkVendorId %d"), (int32)VendorId); bUseVendorIdAsIs = true; break; } } if (bUseVendorIdAsIs) { GRHIVendorId = Props.vendorID; } GRHIAdapterName = ANSI_TO_TCHAR(Props.deviceName); if (PLATFORM_ANDROID) { GRHIAdapterName.Append(TEXT(" Vulkan")); // On Android GL version string often contains extra information such as an actual driver version on the device. #if PLATFORM_ANDROID FString GLVersion = FAndroidMisc::GetGLVersion(); #else FString GLVersion = ""; #endif GRHIAdapterInternalDriverVersion = FString::Printf(TEXT("%d.%d.%d|%s"), VK_VERSION_MAJOR(Props.apiVersion), VK_VERSION_MINOR(Props.apiVersion), VK_VERSION_PATCH(Props.apiVersion), *GLVersion); UE_LOG(LogVulkanRHI, Log, TEXT("API Version: %s"), *GRHIAdapterInternalDriverVersion); } else if (PLATFORM_WINDOWS) { GRHIDeviceId = Props.deviceID; UE_LOG(LogVulkanRHI, Log, TEXT("API Version: %d.%d.%d"), VK_VERSION_MAJOR(Props.apiVersion), VK_VERSION_MINOR(Props.apiVersion), VK_VERSION_PATCH(Props.apiVersion)); } else if(PLATFORM_UNIX) { if (Device->GetVendorId() == EGpuVendorId::Nvidia) { UNvidiaDriverVersion NvidiaVersion; static_assert(sizeof(NvidiaVersion) == sizeof(Props.driverVersion), "Mismatched Nvidia pack driver version!"); NvidiaVersion.Packed = Props.driverVersion; GRHIAdapterUserDriverVersion = FString::Printf(TEXT("%d.%02d"), NvidiaVersion.Major, NvidiaVersion.Minor); } else { GRHIAdapterUserDriverVersion = FString::Printf(TEXT("%d.%d.%d"), VK_VERSION_MAJOR(Props.driverVersion), VK_VERSION_MINOR(Props.driverVersion), VK_VERSION_PATCH(Props.driverVersion)); } GRHIDeviceId = Props.deviceID; GRHIAdapterInternalDriverVersion = GRHIAdapterUserDriverVersion; GRHIAdapterDriverDate = TEXT("01-01-01"); // Unused on unix systems, pick a date that will fail test if compared but passes IsValid() check UE_LOG(LogVulkanRHI, Log, TEXT(" API Version: %d.%d.%d"), VK_VERSION_MAJOR(Props.apiVersion), VK_VERSION_MINOR(Props.apiVersion), VK_VERSION_PATCH(Props.apiVersion)); } GRHIPersistentThreadGroupCount = 1440; // TODO: Revisit based on vendor/adapter/perf query GRHIGlobals.SupportsTimestampRenderQueries = FVulkanPlatform::SupportsTimestampRenderQueries() && (Device->GetLimits().timestampPeriod > 0.0f); } void FVulkanDynamicRHI::InitInstance() { check(IsInGameThread()); if (!GIsRHIInitialized) { // Wait for the rendering thread to go idle. FlushRenderingCommands(); FVulkanPlatform::OverridePlatformHandlers(true); GRHISupportsAsyncTextureCreation = false; Device->InitGPU(); #if VULKAN_HAS_DEBUGGING_ENABLED if (GRenderDocFound) { EnableIdealGPUCaptureOptions(true); } #endif const VkPhysicalDeviceProperties& Props = Device->GetDeviceProperties(); const VkPhysicalDeviceLimits& Limits = Device->GetLimits(); // Initialize the RHI capabilities. GRHISupportsFirstInstance = true; GRHISupportsDynamicResolution = FVulkanPlatform::SupportsDynamicResolution(); GRHISupportsFrameCyclesBubblesRemoval = true; GSupportsDepthBoundsTest = Device->GetPhysicalDeviceFeatures().Core_1_0.depthBounds != 0; GSupportsRenderTargetFormat_PF_G8 = false; // #todo-rco GRHISupportsTextureStreaming = true; GRHISupportsGPUTimestampBubblesRemoval = true; GSupportsMobileMultiView = Device->GetOptionalExtensions().HasKHRMultiview ? true : false; GRHISupportsMSAAShaderResolve = Device->GetOptionalExtensions().HasQcomRenderPassShaderResolve ? true : false; GRHISupportsRayTracing = RHI_RAYTRACING && RHISupportsRayTracing(GMaxRHIShaderPlatform) && Device->GetOptionalExtensions().HasRaytracingExtensions(); GRHIGlobals.SupportsMapWriteNoOverwrite = true; GRHIGlobals.NeedsExtraTransitions = true; // Use this compatibility mode avoid known issues at launch time with latest drivers at the time of release 5.5. This will: // - disable inline ray tracing and use ray tracing pipelines everywhere (instead of a mix of both) // - disable mesh shaders until issues can be resolved (holes in Nanite meshes) // - disable ray tracing for RADV driver prior to version 24.3.2 in Linux (raytracing pipeline compilation crash) // :todo-jn: to be removed when the official minimum RADV version is set to 24.3.2 in BaseHardware.ini const bool bUseAMDCompatibilityMode = GVulkanAMDCompatibilityMode && (Device->GetVendorId() == EGpuVendorId::Amd); if (GVulkanAMDCompatibilityMode && (Device->GetOptionalExtensionProperties().PhysicalDeviceDriverProperties.driverID == VK_DRIVER_ID_MESA_RADV) && (Props.driverVersion < VK_MAKE_VERSION(24, 3, 2))) { GRHISupportsRayTracing = false; GRHISupportsRayTracingShaders = false; GRHISupportsInlineRayTracing = false; UE_LOG(LogVulkanRHI, Warning, TEXT("Using MESA RADV version prior to 24.3.2, ray tracing disabled.")); } if (GRHISupportsRayTracing) { GRHISupportsRayTracingShaders = RHISupportsRayTracingShaders(GMaxRHIShaderPlatform) && Device->GetOptionalExtensions().HasRayTracingPipeline; GRHISupportsInlineRayTracing = !bUseAMDCompatibilityMode && RHISupportsInlineRayTracing(GMaxRHIShaderPlatform) && Device->GetOptionalExtensions().HasRayQuery; // Inline RayTracing SBT is needed if raytracing position fetch isn't available GRHIGlobals.RayTracing.RequiresInlineRayTracingSBT = !VULKAN_SUPPORTS_RAY_TRACING_POSITION_FETCH; GRHIRayTracingAccelerationStructureAlignment = 256; // TODO (currently handled by FVulkanAccelerationStructureBuffer) //Some devices have 64 for min AS offset alignment meanwhile engine AS alignment is 256. hence using round up value GRHIRayTracingScratchBufferAlignment = FPlatformMath::Max(GRHIRayTracingAccelerationStructureAlignment, Device->GetOptionalExtensionProperties().AccelerationStructureProps.minAccelerationStructureScratchOffsetAlignment); GRHIRayTracingInstanceDescriptorSize = uint32(sizeof(VkAccelerationStructureInstanceKHR)); // Loose parameters are always placed in the shader record after the FVulkanHitGroupSystemParameters in Vulkan (see VulkanRayTracing.h and VulkanCommon.ush) GRHIGlobals.RayTracing.SupportsLooseParamsInShaderRecord = true; } #if VULKAN_ENABLE_DUMP_LAYER // Disable RHI thread by default if the dump layer is enabled GRHISupportsRHIThread = false; GRHISupportsParallelRHIExecute = false; #else GRHISupportsRHIThread = GRHIThreadCvar->GetInt() != 0; GRHISupportsParallelRHIExecute = Device->SupportsParallelRendering() && (GRHIThreadCvar->GetInt() > 1); #endif GRHISupportsParallelRenderPasses = GRHISupportsParallelRHIExecute; GRHIParallelRHIExecuteChildWait = GRHISupportsParallelRHIExecute; GRHIParallelRHIExecuteParentWait = GRHISupportsParallelRHIExecute; GRHISupportsUAVFormatAliasing = true; // Some platforms might only have CPU for an RHI thread, but not for parallel tasks GSupportsParallelRenderingTasksWithSeparateRHIThread = GRHISupportsRHIThread ? FVulkanPlatform::SupportParallelRenderingTasks() : false; //#todo-rco: Add newer Nvidia also GSupportsEfficientAsyncCompute = Device->HasAsyncComputeQueue(); UE_LOG(LogVulkanRHI, Display, TEXT("Vulkan Async Compute has been %s."), GSupportsEfficientAsyncCompute ? TEXT("ENABLED") : TEXT("DISABLED")); GSupportsVolumeTextureRendering = FVulkanPlatform::SupportsVolumeTextureRendering(); // Indicate that the RHI needs to use the engine's deferred deletion queue. GRHINeedsExtraDeletionLatency = true; GMaxShadowDepthBufferSizeX = FPlatformMath::Min(Props.limits.maxImageDimension2D, GMaxShadowDepthBufferSizeX); GMaxShadowDepthBufferSizeY = FPlatformMath::Min(Props.limits.maxImageDimension2D, GMaxShadowDepthBufferSizeY); GMaxTextureDimensions = Props.limits.maxImageDimension2D; GRHIGlobals.MaxViewDimensionForTypedBuffer = Props.limits.maxTexelBufferElements; GRHIGlobals.MaxViewSizeBytesForNonTypedBuffer = Props.limits.maxStorageBufferRange; GMaxComputeSharedMemory = Props.limits.maxComputeSharedMemorySize; GMaxTextureMipCount = FPlatformMath::CeilLogTwo( GMaxTextureDimensions ) + 1; GMaxTextureMipCount = FPlatformMath::Min( MAX_TEXTURE_MIP_COUNT, GMaxTextureMipCount ); GMaxCubeTextureDimensions = Props.limits.maxImageDimensionCube; GMaxVolumeTextureDimensions = Props.limits.maxImageDimension3D; GMaxWorkGroupInvocations = Props.limits.maxComputeWorkGroupInvocations; GMaxTextureArrayLayers = Props.limits.maxImageArrayLayers; GRHISupportsBaseVertexIndex = true; GSupportsSeparateRenderTargetBlendState = true; GSupportsDualSrcBlending = Device->GetPhysicalDeviceFeatures().Core_1_0.dualSrcBlend == VK_TRUE; GRHISupportsSeparateDepthStencilCopyAccess = Device->SupportsParallelRendering(); GRHIGlobals.bSupportsBindless = Device->SupportsBindless() && FDataDrivenShaderPlatformInfo::GetSupportsBindless(GMaxRHIShaderPlatform); GMaxTextureSamplers = (int32)FMath::Min(MAX_int32, Props.limits.maxPerStageDescriptorSamplers); GRHISupportsLossyFramebufferCompression = Device->GetOptionalExtensions().HasEXTImageCompressionControl; GRHIMaxDispatchThreadGroupsPerDimension.X = FMath::Min(Limits.maxComputeWorkGroupCount[0], 0x7fffffff); GRHIMaxDispatchThreadGroupsPerDimension.Y = FMath::Min(Limits.maxComputeWorkGroupCount[1], 0x7fffffff); GRHIMaxDispatchThreadGroupsPerDimension.Z = FMath::Min(Limits.maxComputeWorkGroupCount[2], 0x7fffffff); GRHISupportsBindingTexArrayPerSlice = true; // Note: While the 2022/2024 profile limits state a minimum of 16, other profiles (even core) go down to 4 // (see https://vulkan.lunarg.com/doc/view/1.3.290.0/windows/profiles_definitions.html) // Since the RHI has historically always supported 8 UAV's, let's leave those specific devices out uint32 DeviceMaxStorageDescriptorPerStage = FMath::Min(Props.limits.maxPerStageDescriptorStorageBuffers, Props.limits.maxPerStageDescriptorStorageImages); GRHIGlobals.MaxSimultaneousUAVs = DeviceMaxStorageDescriptorPerStage >= 16 ? 16 : 8; FVulkanPlatform::SetupFeatureLevels(GRHIGlobals.ShaderPlatformForFeatureLevel); GRHIRequiresRenderTargetForPixelShaderUAVs = true; GUseTexture3DBulkDataRHI = false; // these are supported by all devices GVulkanDevicePipelineStageBits = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; VkShaderStageFlags VulkanDeviceShaderStageBits = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_COMPUTE_BIT; // optional shader stages if (Device->GetPhysicalDeviceFeatures().Core_1_0.geometryShader) { GVulkanDevicePipelineStageBits |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT; VulkanDeviceShaderStageBits |= VK_SHADER_STAGE_GEOMETRY_BIT; } #if PLATFORM_SUPPORTS_MESH_SHADERS // If mesh shaders are enabled in DDPI (currently SM6), then the profile check will ensure it's supported if (!bUseAMDCompatibilityMode && Device->GetOptionalExtensions().HasEXTMeshShader) { GRHIGlobals.SupportsMeshShadersTier0 = RHISupportsMeshShadersTier0(GMaxRHIShaderPlatform); GRHIGlobals.SupportsMeshShadersTier1 = RHISupportsMeshShadersTier1(GMaxRHIShaderPlatform); GVulkanDevicePipelineStageBits |= VK_PIPELINE_STAGE_TASK_SHADER_BIT_EXT | VK_PIPELINE_STAGE_MESH_SHADER_BIT_EXT; VulkanDeviceShaderStageBits |= VK_SHADER_STAGE_TASK_BIT_EXT | VK_SHADER_STAGE_MESH_BIT_EXT; } #endif const VkShaderStageFlags RequiredSubgroupShaderStageFlags = FVulkanPlatform::RequiredWaveOpsShaderStageFlags(VulkanDeviceShaderStageBits); // Check for wave ops support (only filled on platforms creating Vulkan 1.1 or greater instances) const VkSubgroupFeatureFlags RequiredSubgroupFlags = VK_SUBGROUP_FEATURE_BASIC_BIT | VK_SUBGROUP_FEATURE_VOTE_BIT | VK_SUBGROUP_FEATURE_ARITHMETIC_BIT | VK_SUBGROUP_FEATURE_BALLOT_BIT | VK_SUBGROUP_FEATURE_SHUFFLE_BIT; GRHISupportsWaveOperations = VKHasAllFlags(Device->GetDeviceSubgroupProperties().supportedStages, RequiredSubgroupShaderStageFlags) && VKHasAllFlags(Device->GetDeviceSubgroupProperties().supportedOperations, RequiredSubgroupFlags); if (GRHISupportsWaveOperations) { // Use default size if VK_EXT_subgroup_size_control didn't fill them if (!GRHIMinimumWaveSize || !GRHIMaximumWaveSize) { GRHIMinimumWaveSize = GRHIMaximumWaveSize = Device->GetDeviceSubgroupProperties().subgroupSize; } UE_LOG(LogVulkanRHI, Display, TEXT("Wave Operations have been ENABLED (wave size: min=%d max=%d)."), GRHIMinimumWaveSize, GRHIMaximumWaveSize); } else { const uint32 MissingStageFlags = (Device->GetDeviceSubgroupProperties().supportedStages & RequiredSubgroupShaderStageFlags) ^ RequiredSubgroupShaderStageFlags; const uint32 MissingOperationFlags = (Device->GetDeviceSubgroupProperties().supportedOperations & RequiredSubgroupFlags) ^ RequiredSubgroupFlags; UE_LOG(LogVulkanRHI, Display, TEXT("Wave Operations have been DISABLED (missing stages=0x%x operations=0x%x)."), MissingStageFlags, MissingOperationFlags); } FHardwareInfo::RegisterHardwareInfo(NAME_RHI, TEXT("Vulkan")); SavePipelineCacheCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.SavePipelineCache"), TEXT("Save pipeline cache."), FConsoleCommandDelegate::CreateStatic(SavePipelineCache), ECVF_Default ); RebuildPipelineCacheCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.RebuildPipelineCache"), TEXT("Rebuilds pipeline cache."), FConsoleCommandDelegate::CreateStatic(RebuildPipelineCache), ECVF_Default ); #if VULKAN_SUPPORTS_VALIDATION_CACHE #if VULKAN_HAS_DEBUGGING_ENABLED if (GValidationCvar.GetValueOnAnyThread() > 0) { SaveValidationCacheCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.SaveValidationCache"), TEXT("Save validation cache."), FConsoleCommandDelegate::CreateStatic(SaveValidationCache), ECVF_Default ); } #endif #endif #if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT DumpMemoryCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.DumpMemory"), TEXT("Dumps memory map."), FConsoleCommandDelegate::CreateStatic(DumpMemory), ECVF_Default ); DumpMemoryFullCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.DumpMemoryFull"), TEXT("Dumps full memory map."), FConsoleCommandDelegate::CreateStatic(DumpMemoryFull), ECVF_Default ); DumpStagingMemoryCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.DumpStagingMemory"), TEXT("Dumps staging memory map."), FConsoleCommandDelegate::CreateStatic(DumpStagingMemory), ECVF_Default ); DumpLRUCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.DumpPSOLRU"), TEXT("Dumps Vulkan PSO LRU."), FConsoleCommandDelegate::CreateStatic(DumpLRU), ECVF_Default ); TrimLRUCmd = IConsoleManager::Get().RegisterConsoleCommand( TEXT("r.Vulkan.TrimPSOLRU"), TEXT("Trim Vulkan PSO LRU."), FConsoleCommandDelegate::CreateStatic(TrimLRU), ECVF_Default ); #endif #if PLATFORM_WINDOWS || PLATFORM_UNIX GRHIDeviceIsIntegrated = (Device->GetDeviceProperties().deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU); UE_LOG(LogVulkanRHI, Log, TEXT("Integrated GPU (iGPU): %s"), GRHIDeviceIsIntegrated ? TEXT("true") : TEXT("false")); #endif InitializeSubmissionPipe(); FRenderResource::InitPreRHIResources(); GIsRHIInitialized = true; } } void FVulkanDynamicRHI::RHIEndFrame_RenderThread(FRHICommandListImmediate& RHICmdList) { // @todo dev-pr - refactor RHIEndFrame_RenderThread to reduce use of the immediate command list, and move cleanup work to RHIEndFrame() below. RHICmdList.EnqueueLambdaMultiPipe(ERHIPipeline::Graphics, FRHICommandListBase::EThreadFence::Enabled, TEXT("Vulkan EndFrame"), [this](FVulkanContextArray const& Contexts) { FVulkanCommandListContext& Context = *Contexts[ERHIPipeline::Graphics]; check(Context.IsImmediate()); #if (RHI_NEW_GPU_PROFILER == 0) Context.GpuProfiler.EndFrame(); #endif bool bTrimMemory = false; Context.FreeUnusedCmdBuffers(bTrimMemory); Context.Device.GetStagingManager().ProcessPendingFree(false, true); Context.Device.GetMemoryManager().ReleaseFreedPages(Context); Context.Device.GetDeferredDeletionQueue().ReleaseResources(); if (UseVulkanDescriptorCache()) { Context.Device.GetDescriptorSetCache().GC(); } Context.Device.GetDescriptorPoolsManager().GC(); Context.Device.RemoveStaleQueryPools(); Context.Device.GetPipelineStateCache()->TickLRU(); Context.Device.GetBindlessDescriptorManager()->UpdateUBAllocator(); Context.Device.GetTempBlockAllocator().UpdateBlocks(); }); FDynamicRHI::RHIEndFrame_RenderThread(RHICmdList); RHICmdList.EnqueueLambdaMultiPipe(ERHIPipeline::Graphics, FRHICommandListBase::EThreadFence::Enabled, TEXT("Vulkan BeginFrame"), [this](FVulkanContextArray const& Contexts) { FVulkanCommandListContext& Context = *Contexts[ERHIPipeline::Graphics]; check(Context.IsImmediate()); extern uint32 GVulkanRHIDeletionFrameNumber; ++GVulkanRHIDeletionFrameNumber; #if (RHI_NEW_GPU_PROFILER == 0) Context.GpuProfiler.BeginFrame(); #endif if (GRHISupportsRayTracing) { Context.Device.GetRayTracingCompactionRequestHandler()->Update(Context); } }); } void FVulkanDynamicRHI::RHIEndFrame(const FRHIEndFrameArgs& Args) { #if RHI_NEW_GPU_PROFILER // Close the previous frame's timing and start a new one auto Lambda = [this, OldTiming = MoveTemp(CurrentTimingPerQueue)]() { TArray> Streams; for (auto const& Timing : OldTiming) { Streams.Add(MoveTemp(Timing->EventStream)); } UE::RHI::GPUProfiler::ProcessEvents(Streams); }; EnqueueEndOfPipeTask(MoveTemp(Lambda), [&](FVulkanPayload& Payload) { // Modify the payloads the EOP task will submit to include // a new timing struct and a frame boundary event. Payload.Timing = CurrentTimingPerQueue.CreateNew(Payload.Queue); ERHIPipeline Pipeline; switch (Payload.Queue.QueueType) { default: checkNoEntry(); [[fallthrough]]; case EVulkanQueueType::Graphics: Pipeline = ERHIPipeline::Graphics; break; case EVulkanQueueType::AsyncCompute: Pipeline = ERHIPipeline::AsyncCompute; break; case EVulkanQueueType::Transfer: // There is currently no high level RHI copy queue support Pipeline = ERHIPipeline::None; break; } // CPU timestamp for the frame boundary event is filled in by the submission thread Payload.EndFrameEvent = UE::RHI::GPUProfiler::FEvent::FFrameBoundary(0, Args.FrameNumber #if WITH_RHI_BREADCRUMBS , (Pipeline != ERHIPipeline::None) ? Args.GPUBreadcrumbs[Pipeline] : nullptr #endif #if STATS , Args.StatsFrame #endif ); }); #else { FVulkanPlatformCommandList* Payloads = new FVulkanPlatformCommandList; FVulkanPayload* Payload = new FVulkanPayload(*Device->GetGraphicsQueue()); Payload->bEndFrame = true; Payloads->Add(Payload); PendingPayloadsForSubmission.Enqueue(Payloads); } #endif // Pump the interrupt queue to gather completed events // (required if we're not using an interrupt thread). ProcessInterruptQueueUntil(nullptr); } #if RHI_NEW_GPU_PROFILER FVulkanTiming::FVulkanTiming(FVulkanQueue& InQueue) : Queue(InQueue) , EventStream(InQueue.GetProfilerQueue()) { } #endif void FVulkanCommandListContext::RHIBeginDrawingViewport(FRHIViewport* ViewportRHI, FRHITexture* RenderTargetRHI) { //FRCLog::Printf(FString::Printf(TEXT("FVulkanCommandListContext::RHIBeginDrawingViewport\n"))); check(ViewportRHI); FVulkanViewport* Viewport = ResourceCast(ViewportRHI); FVulkanDynamicRHI::Get().DrawingViewport = Viewport; FRHICustomPresent* CustomPresent = Viewport->GetCustomPresent(); if (CustomPresent) { CustomPresent->BeginDrawing(); } } void FVulkanCommandListContext::RHIEndDrawingViewport(FRHIViewport* ViewportRHI, bool bPresent, bool bLockToVsync) { LLM_SCOPE_VULKAN(ELLMTagVulkan::VulkanMisc); //FRCLog::Printf(FString::Printf(TEXT("FVulkanCommandListContext::RHIEndDrawingViewport()"))); check(IsImmediate()); FVulkanViewport* Viewport = ResourceCast(ViewportRHI); check(Viewport == FVulkanDynamicRHI::Get().DrawingViewport); //#todo-rco: Unbind all pending state /* check(bPresent); RHI->Present(); */ FVulkanCommandBuffer& CommandBuffer = GetCommandBuffer(); check(!CommandBuffer.HasEnded() && !CommandBuffer.IsInsideRenderPass()); bool bNativePresent = Viewport->Present(*this, Device.GetPresentQueue(), bLockToVsync); if (bNativePresent) { //#todo-rco: Check for r.FinishCurrentFrame } FVulkanDynamicRHI::Get().DrawingViewport = nullptr; } #if WITH_RHI_BREADCRUMBS void FVulkanCommandListContext::RHIBeginBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb) { const TCHAR* NameStr = nullptr; FRHIBreadcrumb::FBuffer Buffer; auto GetNameStr = [&]() { if (!NameStr) { NameStr = Breadcrumb->GetTCHAR(Buffer); } return NameStr; }; const FColor Color = FColor::White; if (ShouldEmitBreadcrumbs()) { #if VULKAN_ENABLE_DRAW_MARKERS if (auto CmdBeginLabel = Device.GetCmdBeginDebugLabel()) { FTCHARToUTF8 Converter(GetNameStr()); VkDebugUtilsLabelEXT Label; ZeroVulkanStruct(Label, VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT); Label.pLabelName = Converter.Get(); FLinearColor LColor(Color); Label.color[0] = LColor.R; Label.color[1] = LColor.G; Label.color[2] = LColor.B; Label.color[3] = LColor.A; CmdBeginLabel(GetCommandBuffer().GetHandle(), &Label); } #endif #if VULKAN_ENABLE_DUMP_LAYER // only valid on immediate context currently. needs to be fixed for parallel rhi execute if (IsImmediate()) { VulkanRHI::DumpLayerPushMarker(GetNameStr()); } #endif } #if RHI_NEW_GPU_PROFILER if (bSupportsBreadcrumbs) { FlushProfilerStats(); auto& Event = GetCommandBuffer().EmplaceProfilerEvent(Breadcrumb); FVulkanQueryPool* CurrentPool = GetCurrentTimestampQueryPool(); const uint32 IndexInPool = CurrentPool->ReserveQuery(&Event.GPUTimestampTOP); VulkanRHI::vkCmdWriteTimestamp(GetCommandBuffer().GetHandle(), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, CurrentPool->GetHandle(), IndexInPool); } #else if (IsImmediate()) { #if VULKAN_SUPPORTS_GPU_CRASH_DUMPS if (GpuProfiler.bTrackingGPUCrashData) { GpuProfiler.PushMarkerForCrash(GetActiveCmdBuffer(), Device.GetCrashMarkerBuffer(), GetNameStr()); } #endif if (GpuProfiler.IsProfilingGPU()) { GpuProfiler.PushEvent(GetNameStr(), Color); } } #endif // RHI_NEW_GPU_PROFILER } void FVulkanCommandListContext::RHIEndBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb) { #if RHI_NEW_GPU_PROFILER if (bSupportsBreadcrumbs) { FlushProfilerStats(); auto& Event = GetCommandBuffer().EmplaceProfilerEvent(Breadcrumb); FVulkanQueryPool* CurrentPool = GetCurrentTimestampQueryPool(); const uint32 IndexInPool = CurrentPool->ReserveQuery(&Event.GPUTimestampBOP); VulkanRHI::vkCmdWriteTimestamp(GetCommandBuffer().GetHandle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, CurrentPool->GetHandle(), IndexInPool); } #else //only valid on immediate context currently. needs to be fixed for parallel rhi execute if (IsImmediate()) { if (GpuProfiler.IsProfilingGPU()) { GpuProfiler.PopEvent(); } #if VULKAN_SUPPORTS_GPU_CRASH_DUMPS if (GpuProfiler.bTrackingGPUCrashData) { GpuProfiler.PopMarkerForCrash(GetActiveCmdBuffer(), Device.GetCrashMarkerBuffer()); } #endif } #endif // RHI_NEW_GPU_PROFILER if (ShouldEmitBreadcrumbs()) { #if VULKAN_ENABLE_DUMP_LAYER if (IsImmediate()) { VulkanRHI::DumpLayerPopMarker(); } #endif #if VULKAN_ENABLE_DRAW_MARKERS if (auto CmdEndLabel = Device.GetCmdEndDebugLabel()) { CmdEndLabel(GetCommandBuffer().GetHandle()); } #endif } } #endif // WITH_RHI_BREADCRUMBS void FVulkanDynamicRHI::RHIGetSupportedResolution( uint32 &Width, uint32 &Height ) { } bool FVulkanDynamicRHI::RHIGetAvailableResolutions(FScreenResolutionArray& Resolutions, bool bIgnoreRefreshRate) { return false; } void FVulkanDynamicRHI::RHIFlushResources() { FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext(); bool bTrimMemory = true; ImmediateContext.FreeUnusedCmdBuffers(bTrimMemory); } // IVulkanDynamicRHI interface uint32 FVulkanDynamicRHI::RHIGetVulkanVersion() const { return ApiVersion; } VkInstance FVulkanDynamicRHI::RHIGetVkInstance() const { return GetInstance(); } VkDevice FVulkanDynamicRHI::RHIGetVkDevice() const { if (Device) { return Device->GetInstanceHandle(); } return VK_NULL_HANDLE; } const uint8* FVulkanDynamicRHI::RHIGetVulkanDeviceUUID() const { return GetDevice()->GetDeviceIdProperties().deviceUUID; } VkPhysicalDevice FVulkanDynamicRHI::RHIGetVkPhysicalDevice() const { return Device->GetPhysicalHandle(); } const VkAllocationCallbacks* FVulkanDynamicRHI::RHIGetVkAllocationCallbacks() { return VULKAN_CPU_ALLOCATOR; } VkQueue FVulkanDynamicRHI::RHIGetGraphicsVkQueue() const { return GetDevice()->GetGraphicsQueue()->GetHandle(); } uint32 FVulkanDynamicRHI::RHIGetGraphicsQueueIndex() const { return GetDevice()->GetGraphicsQueue()->GetQueueIndex(); } uint32 FVulkanDynamicRHI::RHIGetGraphicsQueueFamilyIndex() const { return GetDevice()->GetGraphicsQueue()->GetFamilyIndex(); } VkCommandBuffer FVulkanDynamicRHI::RHIGetActiveVkCommandBuffer() { FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext(); VkCommandBuffer VulkanCommandBuffer = ImmediateContext.GetActiveCmdBuffer()->GetHandle(); return VulkanCommandBuffer; } uint64 FVulkanDynamicRHI::RHIGetGraphicsAdapterLUID(VkPhysicalDevice InPhysicalDevice) const { uint64 AdapterLUID = 0; #if VULKAN_SUPPORTS_DRIVER_PROPERTIES VkPhysicalDeviceProperties2KHR GpuProps2; ZeroVulkanStruct(GpuProps2, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2); VkPhysicalDeviceIDPropertiesKHR GpuIdProps; ZeroVulkanStruct(GpuIdProps, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ID_PROPERTIES); GpuProps2.pNext = &GpuIdProps; VulkanRHI::vkGetPhysicalDeviceProperties2(InPhysicalDevice, &GpuProps2); check(GpuIdProps.deviceLUIDValid); AdapterLUID = reinterpret_cast(GpuIdProps.deviceLUID); #endif return AdapterLUID; } bool FVulkanDynamicRHI::RHIDoesAdapterMatchDevice(const void* InAdapterId) const { #if PLATFORM_WINDOWS const VkPhysicalDeviceIDPropertiesKHR& vkPhysicalDeviceIDProperties = GetDevice()->GetDeviceIdProperties(); if (vkPhysicalDeviceIDProperties.deviceLUIDValid) { return FMemory::Memcmp(InAdapterId, &vkPhysicalDeviceIDProperties.deviceLUID, sizeof(LUID)) == 0; } #endif // Not enough information. Assume the adapter matches return true; } void* FVulkanDynamicRHI::RHIGetVkDeviceProcAddr(const char* InName) const { return (void*)VulkanRHI::vkGetDeviceProcAddr(Device->GetInstanceHandle(), InName); } void* FVulkanDynamicRHI::RHIGetVkInstanceProcAddr(const char* InName) const { return (void*)VulkanRHI::vkGetInstanceProcAddr(Instance, InName); } void* FVulkanDynamicRHI::RHIGetVkInstanceGlobalProcAddr(const char* InName) const { return (void*)VulkanRHI::vkGetInstanceProcAddr(nullptr, InName); } VkFormat FVulkanDynamicRHI::RHIGetSwapChainVkFormat(EPixelFormat InFormat) const { // UE renders a gamma-corrected image so we need to use an sRGB format if available return UEToVkTextureFormat(GPixelFormats[InFormat].UnrealFormat, true); } bool FVulkanDynamicRHI::RHISupportsEXTFragmentDensityMap2() const { return GetDevice()->GetOptionalExtensions().HasEXTFragmentDensityMap2; } TArray FVulkanDynamicRHI::RHIGetAllInstanceExtensions() const { uint32_t ExtensionCount = 0; VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &ExtensionCount, nullptr); TArray Extensions; Extensions.SetNumUninitialized(ExtensionCount); VulkanRHI::vkEnumerateInstanceExtensionProperties(nullptr, &ExtensionCount, Extensions.GetData()); return Extensions; } TArray FVulkanDynamicRHI::RHIGetAllDeviceExtensions(VkPhysicalDevice InPhysicalDevice) const { uint32_t ExtensionCount = 0; VulkanRHI::vkEnumerateDeviceExtensionProperties(InPhysicalDevice, nullptr, &ExtensionCount, nullptr); TArray Extensions; Extensions.SetNumUninitialized(ExtensionCount); VulkanRHI::vkEnumerateDeviceExtensionProperties(InPhysicalDevice, nullptr, &ExtensionCount, Extensions.GetData()); return Extensions; } TArray FVulkanDynamicRHI::RHIGetLoadedDeviceExtensions() const { // Create copies to prevent issues TArray OutExtensions; const TArray& DeviceExtensions = GetDevice()->DeviceExtensions; for (const ANSICHAR* ExtensionName : DeviceExtensions) { OutExtensions.Emplace(ExtensionName); } return OutExtensions; } VkImage FVulkanDynamicRHI::RHIGetVkImage(FRHITexture* InTexture) const { FVulkanTexture* VulkanTexture = ResourceCast(InTexture); return VulkanTexture->Image; } VkFormat FVulkanDynamicRHI::RHIGetViewVkFormat(FRHITexture* InTexture) const { FVulkanTexture* VulkanTexture = ResourceCast(InTexture); return VulkanTexture->ViewFormat; } FVulkanRHIAllocationInfo FVulkanDynamicRHI::RHIGetAllocationInfo(FRHITexture* InTexture) const { FVulkanTexture* VulkanTexture = ResourceCast(InTexture); FVulkanRHIAllocationInfo NewInfo{}; NewInfo.Handle = VulkanTexture->GetAllocationHandle(); NewInfo.Offset = VulkanTexture->GetAllocationOffset(); NewInfo.Size = VulkanTexture->GetMemorySize(); return NewInfo; } FVulkanRHIImageViewInfo FVulkanDynamicRHI::RHIGetImageViewInfo(FRHITexture* InTexture) const { FVulkanTexture* VulkanTexture = ResourceCast(InTexture); const FRHITextureDesc& Desc = InTexture->GetDesc(); FVulkanRHIImageViewInfo Info{}; Info.ImageView = VulkanTexture->DefaultView->GetTextureView().View; Info.Image = VulkanTexture->DefaultView->GetTextureView().Image; Info.Format = VulkanTexture->ViewFormat; Info.Width = Desc.Extent.X; Info.Height = Desc.Extent.Y; Info.Depth = Desc.Depth; Info.UEFlags = Desc.Flags; Info.SubresourceRange.aspectMask = VulkanTexture->GetFullAspectMask(); Info.SubresourceRange.layerCount = VulkanTexture->GetNumberOfArrayLevels(); Info.SubresourceRange.levelCount = Desc.NumMips; // TODO: do we need these? Info.SubresourceRange.baseMipLevel = 0; Info.SubresourceRange.baseArrayLayer = 0; return Info; } FVulkanRHIAllocationInfo FVulkanDynamicRHI::RHIGetAllocationInfo(FRHIBuffer* InBuffer) const { FVulkanBuffer* VulkanBuffer = ResourceCast(InBuffer); const VulkanRHI::FVulkanAllocation& Allocation = VulkanBuffer->GetCurrentAllocation(); FVulkanRHIAllocationInfo NewInfo{}; NewInfo.Handle = Allocation.GetDeviceMemoryHandle(GetDevice()); NewInfo.Offset = Allocation.Offset; NewInfo.Size = Allocation.Size; return NewInfo; } void FVulkanDynamicRHI::RHISetImageLayout(VkImage Image, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresourceRange) { FVulkanCommandListContext& ImmediateContext = GetDevice()->GetImmediateContext(); FVulkanCommandBuffer& CommandBuffer = ImmediateContext.GetCommandBuffer(); VulkanSetImageLayout(CommandBuffer.GetHandle(), Image, OldLayout, NewLayout, SubresourceRange); } void FVulkanDynamicRHI::RHISetUploadImageLayout(VkImage Image, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresourceRange) { FVulkanCommandListContext& ImmediateContext = GetDevice()->GetImmediateContext(); FVulkanCommandBuffer& CommandBuffer = ImmediateContext.GetCommandBuffer(); VulkanSetImageLayout(CommandBuffer.GetHandle(), Image, OldLayout, NewLayout, SubresourceRange); } void FVulkanDynamicRHI::RHIFinishExternalComputeWork(VkCommandBuffer InCommandBuffer) { FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext(); check(InCommandBuffer == ImmediateContext.GetActiveCmdBuffer()->GetHandle()); ImmediateContext.GetPendingComputeState()->Reset(); ImmediateContext.GetPendingGfxState()->Reset(); } void FVulkanDynamicRHI::RHIRegisterWork(uint32 NumPrimitives) { #if (RHI_NEW_GPU_PROFILER == 0) FVulkanCommandListContextImmediate& ImmediateContext = GetDevice()->GetImmediateContext(); if (FVulkanPlatform::RegisterGPUWork() && ImmediateContext.IsImmediate()) { ImmediateContext.RegisterGPUWork(NumPrimitives); } #endif } void FVulkanDynamicRHI::RHISubmitUploadCommandBuffer() { } void FVulkanDynamicRHI::RHIVerifyResult(VkResult Result, const ANSICHAR* VkFuntion, const ANSICHAR* Filename, uint32 Line) { VulkanRHI::VerifyVulkanResult(Result, VkFuntion, Filename, Line); } void* FVulkanDynamicRHI::RHIGetNativeDevice() { return (void*)Device->GetInstanceHandle(); } void* FVulkanDynamicRHI::RHIGetNativePhysicalDevice() { return (void*)Device->GetPhysicalHandle(); } void* FVulkanDynamicRHI::RHIGetNativeGraphicsQueue() { return (void*)Device->GetGraphicsQueue()->GetHandle(); } void* FVulkanDynamicRHI::RHIGetNativeComputeQueue() { return Device->HasAsyncComputeQueue() ? (void*)Device->GetComputeQueue()->GetHandle() : (void*)Device->GetGraphicsQueue()->GetHandle(); } void* FVulkanDynamicRHI::RHIGetNativeInstance() { return (void*)GetInstance(); } IRHICommandContext* FVulkanDynamicRHI::RHIGetDefaultContext() { return &Device->GetImmediateContext(); } uint64 FVulkanDynamicRHI::RHIGetMinimumAlignmentForBufferBackedSRV(EPixelFormat Format) { const VkPhysicalDeviceLimits& Limits = Device->GetLimits(); return Limits.minTexelBufferOffsetAlignment; } FTextureRHIRef FVulkanDynamicRHI::RHICreateTexture2DFromResource(EPixelFormat Format, uint32 SizeX, uint32 SizeY, uint32 NumMips, uint32 NumSamples, VkImage Resource, ETextureCreateFlags Flags, const FClearValueBinding& ClearValueBinding, const FVulkanRHIExternalImageDeleteCallbackInfo& ExternalImageDeleteCallbackInfo) { const FRHITextureCreateDesc Desc = FRHITextureCreateDesc::Create2D(TEXT("VulkanTexture2DFromResource"), SizeX, SizeY, Format) .SetClearValue(ClearValueBinding) .SetFlags(Flags) .SetNumMips(NumMips) .SetNumSamples(NumSamples) .DetermineInititialState(); return new FVulkanTexture(*Device, Desc, Resource, ExternalImageDeleteCallbackInfo); } #if PLATFORM_ANDROID FTextureRHIRef FVulkanDynamicRHI::RHICreateTexture2DFromAndroidHardwareBuffer(AHardwareBuffer* HardwareBuffer) { check(HardwareBuffer); AHardwareBuffer_Desc HardwareBufferDesc; AHardwareBuffer_describe(HardwareBuffer, &HardwareBufferDesc); check((HardwareBufferDesc.usage & AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE) != 0); const FRHITextureCreateDesc Desc = FRHITextureCreateDesc::Create2D(TEXT("VulkanTexture2DFromAndroidHardwareBuffer"), HardwareBufferDesc.width, HardwareBufferDesc.height, PF_Unknown) .SetFlags(ETextureCreateFlags::External) .DetermineInititialState(); return new FVulkanTexture(*Device, Desc, HardwareBufferDesc, HardwareBuffer); } #endif FTextureRHIRef FVulkanDynamicRHI::RHICreateTexture2DArrayFromResource(EPixelFormat Format, uint32 SizeX, uint32 SizeY, uint32 ArraySize, uint32 NumMips, uint32 NumSamples, VkImage Resource, ETextureCreateFlags Flags, const FClearValueBinding& ClearValueBinding) { const FRHITextureCreateDesc Desc = FRHITextureCreateDesc::Create2DArray(TEXT("VulkanTextureArrayFromResource"), SizeX, SizeY, ArraySize, Format) .SetClearValue(ClearValueBinding) .SetFlags(Flags) .SetNumMips(NumMips) .SetNumSamples(NumSamples) .DetermineInititialState(); return new FVulkanTexture(*Device, Desc, Resource, {}); } FTextureRHIRef FVulkanDynamicRHI::RHICreateTextureCubeFromResource(EPixelFormat Format, uint32 Size, bool bArray, uint32 ArraySize, uint32 NumMips, VkImage Resource, ETextureCreateFlags Flags, const FClearValueBinding& ClearValueBinding) { const FRHITextureCreateDesc Desc = FRHITextureCreateDesc::Create(TEXT("VulkanTextureCubeFromResource"), ArraySize > 1 ? ETextureDimension::TextureCubeArray : ETextureDimension::TextureCube) .SetExtent(Size) .SetArraySize(ArraySize) .SetFormat(Format) .SetClearValue(ClearValueBinding) .SetFlags(Flags) .SetNumMips(NumMips) .DetermineInititialState(); return new FVulkanTexture(*Device, Desc, Resource, {}); } void FVulkanDynamicRHI::RHIAliasTextureResources(FTextureRHIRef& DestTextureRHI, FTextureRHIRef& SrcTextureRHI) { if (DestTextureRHI && SrcTextureRHI) { FVulkanTexture* DestTexture = ResourceCast(DestTextureRHI); DestTexture->AliasTextureResources(SrcTextureRHI); } } FTextureRHIRef FVulkanDynamicRHI::RHICreateAliasedTexture(FTextureRHIRef& SourceTextureRHI) { const FString Name = SourceTextureRHI->GetName().ToString() + TEXT("Alias"); FRHITextureCreateDesc Desc(SourceTextureRHI->GetDesc(), ERHIAccess::SRVMask, *Name); return new FVulkanTexture(*Device, Desc, SourceTextureRHI); } FVulkanDescriptorSetsLayout::FVulkanDescriptorSetsLayout(FVulkanDevice* InDevice) : Device(InDevice) { } FVulkanDescriptorSetsLayout::~FVulkanDescriptorSetsLayout() { // Handles are owned by FVulkanPipelineStateCacheManager LayoutHandles.Reset(0); } // Increments a value and asserts on overflow. // FSetInfo uses narrow integer types for descriptor counts, // which may feasibly overflow one day (for example if we add bindless resources). template static void IncrementChecked(T& Value) { check(Value < TNumericLimits::Max()); ++Value; } void FVulkanDescriptorSetsLayoutInfo::AddDescriptor(int32 DescriptorSetIndex, const VkDescriptorSetLayoutBinding& Descriptor) { // Increment type usage if (LayoutTypes.Contains(Descriptor.descriptorType)) { LayoutTypes[Descriptor.descriptorType]++; } else { LayoutTypes.Add(Descriptor.descriptorType, 1); } if (DescriptorSetIndex >= SetLayouts.Num()) { SetLayouts.SetNum(DescriptorSetIndex + 1, EAllowShrinking::No); } FSetLayout& DescSetLayout = SetLayouts[DescriptorSetIndex]; VkDescriptorSetLayoutBinding* Binding = new(DescSetLayout.LayoutBindings) VkDescriptorSetLayoutBinding; *Binding = Descriptor; const FStageInfo& SetInfo = StageInfos[DescriptorSetIndex]; check(SetInfo.Types[Descriptor.binding] == Descriptor.descriptorType); switch (Descriptor.descriptorType) { case VK_DESCRIPTOR_TYPE_SAMPLER: case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE: case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE: case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: IncrementChecked(StageInfos[DescriptorSetIndex].NumImageInfos); break; case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER: case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER: case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC: IncrementChecked(StageInfos[DescriptorSetIndex].NumBufferInfos); break; case VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR: IncrementChecked(StageInfos[DescriptorSetIndex].NumAccelerationStructures); break; case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER: case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER: break; default: checkf(0, TEXT("Unsupported descriptor type %d"), (int32)Descriptor.descriptorType); break; } } void FVulkanDescriptorSetsLayoutInfo::GenerateHash(const TArrayView& InImmutableSamplers, VkPipelineBindPoint InBindPoint) { const int32 LayoutCount = SetLayouts.Num(); Hash = FCrc::MemCrc32(&TypesUsageID, sizeof(uint32), LayoutCount); for (int32 layoutIndex = 0; layoutIndex < LayoutCount; ++layoutIndex) { SetLayouts[layoutIndex].GenerateHash(); Hash = FCrc::MemCrc32(&SetLayouts[layoutIndex].Hash, sizeof(uint32), Hash); } const uint32 NumStages = GetNumStagesForBindPoint(InBindPoint); for (uint32 RemapingIndex = 0; RemapingIndex < NumStages; ++RemapingIndex) { const FStageInfo& StageInfo = StageInfos[RemapingIndex]; Hash = FCrc::TypeCrc32(StageInfo.PackedGlobalsSize, Hash); Hash = FCrc::TypeCrc32(StageInfo.NumBoundUniformBuffers, Hash); Hash = FCrc::TypeCrc32(StageInfo.NumImageInfos, Hash); Hash = FCrc::TypeCrc32(StageInfo.NumBufferInfos, Hash); Hash = FCrc::TypeCrc32(StageInfo.NumAccelerationStructures, Hash); const TArray& Types = StageInfo.Types; Hash = FCrc::MemCrc32(Types.GetData(), sizeof(VkDescriptorType) * Types.Num(), Hash); } // It would be better to store this when the object is created, but it's not available at that time, so we'll do it here. BindPoint = InBindPoint; // Include the bind point in the hash, because we can have graphics and compute PSOs with the same descriptor info, and we don't want them to collide. Hash = FCrc::MemCrc32(&BindPoint, sizeof(BindPoint), Hash); } static FCriticalSection GTypesUsageCS; void FVulkanDescriptorSetsLayoutInfo::CompileTypesUsageID() { FScopeLock ScopeLock(>ypesUsageCS); static TMap GTypesUsageHashMap; static uint32 GUniqueID = 1; LayoutTypes.KeySort([](VkDescriptorType A, VkDescriptorType B) { return static_cast(A) < static_cast(B); }); uint32 TypesUsageHash = 0; for (const auto& Elem : LayoutTypes) { TypesUsageHash = FCrc::MemCrc32(&Elem.Value, sizeof(uint32), TypesUsageHash); } uint32* UniqueID = GTypesUsageHashMap.Find(TypesUsageHash); if (UniqueID == nullptr) { TypesUsageID = GTypesUsageHashMap.Add(TypesUsageHash, GUniqueID++); } else { TypesUsageID = *UniqueID; } } void FVulkanDescriptorSetsLayout::Compile(FVulkanDescriptorSetLayoutMap& DSetLayoutMap) { check(LayoutHandles.Num() == 0); // Check if we obey limits const VkPhysicalDeviceLimits& Limits = Device->GetLimits(); // Check for maxDescriptorSetSamplers check( LayoutTypes[VK_DESCRIPTOR_TYPE_SAMPLER] + LayoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER] <= Limits.maxDescriptorSetSamplers); // Check for maxDescriptorSetUniformBuffers check( LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER] + LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC] <= Limits.maxDescriptorSetUniformBuffers); // Check for maxDescriptorSetUniformBuffersDynamic check(Device->GetVendorId() == EGpuVendorId::Amd || LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC] <= Limits.maxDescriptorSetUniformBuffersDynamic); // Check for maxDescriptorSetStorageBuffers check( LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER] + LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC] <= Limits.maxDescriptorSetStorageBuffers); // Check for maxDescriptorSetStorageBuffersDynamic check( LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC] <= Limits.maxDescriptorSetStorageBuffersDynamic); // Check for maxDescriptorSetSampledImages check( LayoutTypes[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER] + LayoutTypes[VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE] + LayoutTypes[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER] <= Limits.maxDescriptorSetSampledImages); // Check for maxDescriptorSetStorageImages check( LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_IMAGE] + LayoutTypes[VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER] <= Limits.maxDescriptorSetStorageImages); check(LayoutTypes[VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT] <= Limits.maxDescriptorSetInputAttachments); if (GRHISupportsRayTracing) { check(LayoutTypes[VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR] < Device->GetOptionalExtensionProperties().AccelerationStructureProps.maxDescriptorSetAccelerationStructures); } LayoutHandles.Empty(SetLayouts.Num()); if (UseVulkanDescriptorCache()) { LayoutHandleIds.Empty(SetLayouts.Num()); } for (FSetLayout& Layout : SetLayouts) { VkDescriptorSetLayout* LayoutHandle = new(LayoutHandles) VkDescriptorSetLayout; uint32* LayoutHandleId = nullptr; if (UseVulkanDescriptorCache()) { LayoutHandleId = new(LayoutHandleIds) uint32; } if (FVulkanDescriptorSetLayoutEntry* Found = DSetLayoutMap.Find(Layout)) { *LayoutHandle = Found->Handle; if (LayoutHandleId) { *LayoutHandleId = Found->HandleId; } continue; } VkDescriptorSetLayoutCreateInfo DescriptorLayoutInfo; ZeroVulkanStruct(DescriptorLayoutInfo, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); DescriptorLayoutInfo.bindingCount = Layout.LayoutBindings.Num(); DescriptorLayoutInfo.pBindings = Layout.LayoutBindings.GetData(); VERIFYVULKANRESULT(VulkanRHI::vkCreateDescriptorSetLayout(Device->GetInstanceHandle(), &DescriptorLayoutInfo, VULKAN_CPU_ALLOCATOR, LayoutHandle)); if (LayoutHandleId) { *LayoutHandleId = ++GVulkanDSetLayoutHandleIdCounter; } FVulkanDescriptorSetLayoutEntry DescriptorSetLayoutEntry; DescriptorSetLayoutEntry.Handle = *LayoutHandle; DescriptorSetLayoutEntry.HandleId = LayoutHandleId ? *LayoutHandleId : 0; DSetLayoutMap.Add(Layout, DescriptorSetLayoutEntry); } if (TypesUsageID == ~0) { CompileTypesUsageID(); } ZeroVulkanStruct(DescriptorSetAllocateInfo, VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); DescriptorSetAllocateInfo.descriptorSetCount = LayoutHandles.Num(); DescriptorSetAllocateInfo.pSetLayouts = LayoutHandles.GetData(); } FVulkanRenderPass::FVulkanRenderPass(FVulkanDevice& InDevice, const FVulkanRenderTargetLayout& InRTLayout) : Layout(InRTLayout), RenderPass(VK_NULL_HANDLE), NumUsedClearValues(InRTLayout.GetNumUsedClearValues()), Device(InDevice) { INC_DWORD_STAT(STAT_VulkanNumRenderPasses); RenderPass = CreateVulkanRenderPass(InDevice, InRTLayout); } FVulkanRenderPass::~FVulkanRenderPass() { DEC_DWORD_STAT(STAT_VulkanNumRenderPasses); Device.GetDeferredDeletionQueue().EnqueueResource(VulkanRHI::FDeferredDeletionQueue2::EType::RenderPass, RenderPass); RenderPass = VK_NULL_HANDLE; } void FVulkanDynamicRHI::SavePipelineCache() { FString CacheFile = VulkanRHI::GetPipelineCacheFilename(); GVulkanRHI->Device->PipelineStateCache->Save(CacheFile); } void FVulkanDynamicRHI::RebuildPipelineCache() { GVulkanRHI->Device->PipelineStateCache->RebuildCache(); } #if VULKAN_SUPPORTS_VALIDATION_CACHE void FVulkanDynamicRHI::SaveValidationCache() { VkValidationCacheEXT ValidationCache = GVulkanRHI->Device->GetValidationCache(); if (ValidationCache != VK_NULL_HANDLE) { VkDevice Device = GVulkanRHI->Device->GetInstanceHandle(); PFN_vkGetValidationCacheDataEXT vkGetValidationCacheData = (PFN_vkGetValidationCacheDataEXT)(void*)VulkanRHI::vkGetDeviceProcAddr(Device, "vkGetValidationCacheDataEXT"); check(vkGetValidationCacheData); size_t CacheSize = 0; VkResult Result = vkGetValidationCacheData(Device, ValidationCache, &CacheSize, nullptr); if (Result == VK_SUCCESS) { if (CacheSize > 0) { TArray Data; Data.AddUninitialized(CacheSize); Result = vkGetValidationCacheData(Device, ValidationCache, &CacheSize, Data.GetData()); if (Result == VK_SUCCESS) { FString CacheFilename = VulkanRHI::GetValidationCacheFilename(); if (FFileHelper::SaveArrayToFile(Data, *CacheFilename)) { UE_LOG(LogVulkanRHI, Display, TEXT("Saved validation cache file '%s', %d bytes"), *CacheFilename, Data.Num()); } } else { UE_LOG(LogVulkanRHI, Warning, TEXT("Failed to query Vulkan validation cache data, VkResult=%d"), Result); } } } else { UE_LOG(LogVulkanRHI, Warning, TEXT("Failed to query Vulkan validation cache size, VkResult=%d"), Result); } } } #endif #if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT void FVulkanDynamicRHI::DumpMemory() { GVulkanRHI->Device->GetMemoryManager().DumpMemory(false); } void FVulkanDynamicRHI::DumpMemoryFull() { GVulkanRHI->Device->GetMemoryManager().DumpMemory(true); } void FVulkanDynamicRHI::DumpStagingMemory() { GVulkanRHI->Device->GetStagingManager().DumpMemory(); } void FVulkanDynamicRHI::DumpLRU() { GVulkanRHI->Device->PipelineStateCache->LRUDump(); } void FVulkanDynamicRHI::TrimLRU() { GVulkanRHI->Device->PipelineStateCache->LRUDebugEvictAll(); } #endif void FVulkanDynamicRHI::VulkanSetImageLayout(VkCommandBuffer CmdBuffer, VkImage Image, VkImageLayout OldLayout, VkImageLayout NewLayout, const VkImageSubresourceRange& SubresourceRange) { FVulkanPipelineBarrier Barrier; Barrier.AddImageLayoutTransition(Image, OldLayout, NewLayout, SubresourceRange); Barrier.Execute(CmdBuffer); } IRHITransientResourceAllocator* FVulkanDynamicRHI::RHICreateTransientResourceAllocator() { #if VULKAN_SUPPORTS_TRANSIENT_RESOURCE_ALLOCATOR // Only use transient heap on desktop platforms for now // Not compatible with VulkanDescriptorCache for now because it hashes using the 32bit BufferId instead of the VulkanHandle. if (GVulkanEnableTransientResourceAllocator && IsPCPlatform(GMaxRHIShaderPlatform) && !UseVulkanDescriptorCache()) { return new FVulkanTransientResourceAllocator(Device->GetOrCreateTransientHeapCache()); } #endif return nullptr; } uint32 FVulkanDynamicRHI::GetPrecachePSOHashVersion() { static const uint32 PrecacheHashVersion = 3; return PrecacheHashVersion; } // If you modify this function bump then GetPrecachePSOHashVersion, this will invalidate any previous uses of the hash. // i.e. pre-existing PSO caches must be rebuilt. uint64 FVulkanDynamicRHI::RHIComputeStatePrecachePSOHash(const FGraphicsPipelineStateInitializer& Initializer) { struct FHashKey { uint32 VertexDeclaration; uint32 VertexShader; uint32 PixelShader; #if PLATFORM_SUPPORTS_GEOMETRY_SHADERS uint32 GeometryShader; #endif // PLATFORM_SUPPORTS_GEOMETRY_SHADERS #if PLATFORM_SUPPORTS_MESH_SHADERS uint32 MeshShader; uint32 TaskShader; #endif // PLATFORM_SUPPORTS_MESH_SHADERS uint32 BlendState; uint32 RasterizerState; uint32 DepthStencilState; uint32 ImmutableSamplerState; uint32 DrawShadingRate : 8; uint32 PrimitiveType : 8; uint32 bDepthBounds : 1; uint32 bAllowVariableRateShading : 1; uint32 Unused : 14; } HashKey; FMemory::Memzero(&HashKey, sizeof(FHashKey)); // We know for sure that on ARM MALI GPUs vertex decl does not affect PSO const bool bVertexDeclAffectsPSO = (GRHIVendorId != (uint32)EGpuVendorId::Arm); if (bVertexDeclAffectsPSO) { HashKey.VertexDeclaration = Initializer.BoundShaderState.VertexDeclarationRHI ? Initializer.BoundShaderState.VertexDeclarationRHI->GetPrecachePSOHash() : 0; } HashKey.VertexShader = Initializer.BoundShaderState.GetVertexShader() ? GetTypeHash(Initializer.BoundShaderState.GetVertexShader()->GetHash()) : 0; HashKey.PixelShader = Initializer.BoundShaderState.GetPixelShader() ? GetTypeHash(Initializer.BoundShaderState.GetPixelShader()->GetHash()) : 0; #if PLATFORM_SUPPORTS_GEOMETRY_SHADERS HashKey.GeometryShader = Initializer.BoundShaderState.GetGeometryShader() ? GetTypeHash(Initializer.BoundShaderState.GetGeometryShader()->GetHash()) : 0; #endif #if PLATFORM_SUPPORTS_MESH_SHADERS HashKey.MeshShader = Initializer.BoundShaderState.GetMeshShader() ? GetTypeHash(Initializer.BoundShaderState.GetMeshShader()->GetHash()) : 0; HashKey.TaskShader = Initializer.BoundShaderState.GetAmplificationShader() ? GetTypeHash(Initializer.BoundShaderState.GetAmplificationShader()->GetHash()) : 0; #endif FBlendStateInitializerRHI BlendStateInitializerRHI; if (Initializer.BlendState && Initializer.BlendState->GetInitializer(BlendStateInitializerRHI)) { HashKey.BlendState = GetTypeHash(BlendStateInitializerRHI); } FRasterizerStateInitializerRHI RasterizerStateInitializerRHI; if (Initializer.RasterizerState && Initializer.RasterizerState->GetInitializer(RasterizerStateInitializerRHI)) { HashKey.RasterizerState = GetTypeHash(RasterizerStateInitializerRHI); } FDepthStencilStateInitializerRHI DepthStencilStateInitializerRHI; if (Initializer.DepthStencilState && Initializer.DepthStencilState->GetInitializer(DepthStencilStateInitializerRHI)) { HashKey.DepthStencilState = GetTypeHash(DepthStencilStateInitializerRHI); } // Ignore immutable samplers for now //HashKey.ImmutableSamplerState = GetTypeHash(ImmutableSamplerState); HashKey.DrawShadingRate = Initializer.ShadingRate; HashKey.PrimitiveType = Initializer.PrimitiveType; HashKey.bDepthBounds = Initializer.bDepthBounds; HashKey.bAllowVariableRateShading = Initializer.bAllowVariableRateShading; uint64 PrecachePSOHash = CityHash64((const char*)&HashKey, sizeof(FHashKey)); return PrecachePSOHash; } // If you modify this function bump then GetPrecachePSOHashVersion, this will invalidate any previous uses of the hash. // i.e. pre-existing PSO caches must be rebuilt. uint64 FVulkanDynamicRHI::RHIComputePrecachePSOHash(const FGraphicsPipelineStateInitializer& Initializer) { // When compute precache PSO hash we assume a valid state precache PSO hash is already provided uint64 StatePrecachePSOHash = Initializer.StatePrecachePSOHash; if (StatePrecachePSOHash == 0) { StatePrecachePSOHash = RHIComputeStatePrecachePSOHash(Initializer); } // All members which are not part of the state objects struct FNonStateHashKey { uint64 StatePrecachePSOHash; uint32 RenderTargetsEnabled; FGraphicsPipelineStateInitializer::TRenderTargetFormats RenderTargetFormats; FGraphicsPipelineStateInitializer::TRenderTargetFlags RenderTargetFlags; // AJB: temporarily disabling depth stencil properties as they do not appear to be required and it causes us to miss some permutations. // EPixelFormat DepthStencilTargetFormat; // ETextureCreateFlags DepthStencilTargetFlag; uint16 NumSamples; ESubpassHint SubpassHint; uint8 SubpassIndex; uint8 MultiViewCount; bool bHasFragmentDensityAttachment; EConservativeRasterization ConservativeRasterization; } HashKey; FMemory::Memzero(&HashKey, sizeof(FNonStateHashKey)); HashKey.StatePrecachePSOHash = StatePrecachePSOHash; HashKey.RenderTargetsEnabled = Initializer.RenderTargetsEnabled; HashKey.RenderTargetFormats = Initializer.RenderTargetFormats; HashKey.RenderTargetFlags = Initializer.RenderTargetFlags; // HashKey.DepthStencilTargetFormat = Initializer.DepthStencilTargetFormat; // HashKey.DepthStencilTargetFlag = Initializer.DepthStencilTargetFlag; HashKey.NumSamples = Initializer.NumSamples; HashKey.SubpassHint = Initializer.SubpassHint; HashKey.SubpassIndex = Initializer.SubpassIndex; HashKey.MultiViewCount = Initializer.MultiViewCount; HashKey.bHasFragmentDensityAttachment = Initializer.bHasFragmentDensityAttachment; HashKey.ConservativeRasterization = Initializer.ConservativeRasterization; // TODO: check if any RT flags actually affect PSO in VK for (ETextureCreateFlags& Flags : HashKey.RenderTargetFlags) { Flags = Flags & FGraphicsPipelineStateInitializer::RelevantRenderTargetFlagMask; } // HashKey.DepthStencilTargetFlag = (HashKey.DepthStencilTargetFlag & FGraphicsPipelineStateInitializer::RelevantDepthStencilFlagMask); return CityHash64((const char*)&HashKey, sizeof(FNonStateHashKey)); } bool FVulkanDynamicRHI::RHIMatchPrecachePSOInitializers(const FGraphicsPipelineStateInitializer& LHS, const FGraphicsPipelineStateInitializer& RHS) { // first check non pointer objects if (LHS.ImmutableSamplerState != RHS.ImmutableSamplerState || LHS.PrimitiveType != RHS.PrimitiveType || LHS.bDepthBounds != RHS.bDepthBounds || LHS.MultiViewCount != RHS.MultiViewCount || LHS.ShadingRate != RHS.ShadingRate || LHS.bHasFragmentDensityAttachment != RHS.bHasFragmentDensityAttachment || LHS.bAllowVariableRateShading != RHS.bAllowVariableRateShading || LHS.RenderTargetsEnabled != RHS.RenderTargetsEnabled || LHS.RenderTargetFormats != RHS.RenderTargetFormats || !FGraphicsPipelineStateInitializer::RelevantRenderTargetFlagsEqual(LHS.RenderTargetFlags, RHS.RenderTargetFlags) || LHS.DepthStencilTargetFormat != RHS.DepthStencilTargetFormat || !FGraphicsPipelineStateInitializer::RelevantDepthStencilFlagsEqual(LHS.DepthStencilTargetFlag, RHS.DepthStencilTargetFlag) || LHS.NumSamples != RHS.NumSamples || LHS.SubpassHint != RHS.SubpassHint || LHS.SubpassIndex != RHS.SubpassIndex || LHS.StatePrecachePSOHash != RHS.StatePrecachePSOHash || LHS.ConservativeRasterization != RHS.ConservativeRasterization) { return false; } // check the RHI shaders (pointer check for shaders should be fine) if (LHS.BoundShaderState.VertexShaderRHI != RHS.BoundShaderState.VertexShaderRHI || LHS.BoundShaderState.PixelShaderRHI != RHS.BoundShaderState.PixelShaderRHI || LHS.BoundShaderState.GetMeshShader() != RHS.BoundShaderState.GetMeshShader() || LHS.BoundShaderState.GetAmplificationShader() != RHS.BoundShaderState.GetAmplificationShader() || LHS.BoundShaderState.GetGeometryShader() != RHS.BoundShaderState.GetGeometryShader()) { return false; } // Full compare the of the vertex declaration if (!MatchRHIState(LHS.BoundShaderState.VertexDeclarationRHI, RHS.BoundShaderState.VertexDeclarationRHI)) { return false; } // Check actual state content (each initializer can have it's own state and not going through a factory) if (!MatchRHIState(LHS.BlendState, RHS.BlendState) || !MatchRHIState(LHS.RasterizerState, RHS.RasterizerState) || !MatchRHIState(LHS.DepthStencilState, RHS.DepthStencilState) ) { return false; } return true; } void FVulkanDynamicRHI::RHIReplaceResources(FRHICommandListBase& RHICmdList, TArray&& ReplaceInfos) { RHICmdList.EnqueueLambda(TEXT("FVulkanDynamicRHI::RHIReplaceResources"), [ReplaceInfos = MoveTemp(ReplaceInfos)](FRHICommandListBase& ExecutingCmdList) { for (FRHIResourceReplaceInfo const& Info : ReplaceInfos) { switch (Info.GetType()) { default: checkNoEntry(); break; case FRHIResourceReplaceInfo::EType::Buffer: { FVulkanBuffer* Dst = ResourceCast(Info.GetBuffer().Dst); FVulkanBuffer* Src = ResourceCast(Info.GetBuffer().Src); if (Src) { // The source buffer should not have any associated views. check(!Src->HasLinkedViews()); Dst->TakeOwnership(*Src); } else { Dst->ReleaseOwnership(); } Dst->UpdateLinkedViews(); } break; case FRHIResourceReplaceInfo::EType::RTGeometry: { FVulkanRayTracingGeometry* Src = ResourceCast(Info.GetRTGeometry().Src); FVulkanRayTracingGeometry* Dst = ResourceCast(Info.GetRTGeometry().Dst); if (!Src) { TRefCountPtr DeletionProxy = new FVulkanRayTracingGeometry(NoInit); Dst->RemoveCompactionRequest(); Dst->Swap(*DeletionProxy); } else { Dst->Swap(*Src); } } break; } } } ); RHICmdList.RHIThreadFence(true); } #if PLATFORM_SUPPORTS_BINDLESS_RENDERING FRHIResourceCollectionRef FVulkanDynamicRHI::RHICreateResourceCollection(FRHICommandListBase& RHICmdList, TConstArrayView InMembers) { return new FVulkanResourceCollection(RHICmdList, InMembers); } #endif #undef LOCTEXT_NAMESPACE