// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= DynamicRHI.cpp: Dynamically bound Render Hardware Interface implementation. =============================================================================*/ #include "DynamicRHI.h" #include "Misc/MessageDialog.h" #include "Experimental/Containers/HazardPointer.h" #include "Misc/OutputDeviceRedirector.h" #include "HAL/IConsoleManager.h" #include "Misc/App.h" #include "Misc/CommandLine.h" #include "Modules/ModuleManager.h" #include "GenericPlatform/GenericPlatformDriver.h" #include "GenericPlatform/GenericPlatformCrashContext.h" #include "PipelineStateCache.h" #include "RHI.h" #include "RHIFwd.h" #include "DataDrivenShaderPlatformInfo.h" #include "RHICommandList.h" #include "RHIImmutableSamplerState.h" #include "RHIStrings.h" #include "RHITextureReference.h" #include "RHIAllocators.h" static_assert(sizeof(FRayTracingGeometryInstance) <= 104, "Ray tracing instance descriptor is expected to be no more than 104 bytes, as there may be a very large number of them."); #ifndef PLATFORM_ALLOW_NULL_RHI #define PLATFORM_ALLOW_NULL_RHI 0 #endif // Globals. FDynamicRHI* GDynamicRHI = NULL; RHIGetGPUUsageType RHIGetGPUUsage = nullptr; bool bDriverDenylistMessageShown = false; static int32 GWarnOfBadDrivers = true; static FAutoConsoleVariableRef CVarWarnOfBadDrivers( TEXT("r.WarnOfBadDrivers"), GWarnOfBadDrivers, TEXT("Check the current GPU driver on engine startup, warn the user about issues and suggest a specific version.\n") TEXT("The driver denylist is used to check for bad drivers according to their release date and/or driver versions.\n") TEXT(" 0: off\n") TEXT(" 1: check the driver and display a pop-up message if the driver is denylisted (default)"), ECVF_RenderThreadSafe ); static bool GBadDriverWarningIsFatal = false; static FAutoConsoleVariableRef CVarBadDriverWarningIsFatal( TEXT("r.BadDriverWarningIsFatal"), GBadDriverWarningIsFatal, TEXT("If non-zero, trigger a fatal error if a denylisted driver is detected.\n") TEXT("For the fatal error to occur, r.WarnOfBadDrivers must be non-zero.\n") TEXT(" 0: off (default)\n") TEXT(" 1: a fatal error occurs after the out-of-date driver message is dismissed (non-Shipping only)\n"), ECVF_RenderThreadSafe); void InitNullRHI() { // Use the null RHI if it was specified on the command line, or if a commandlet is running. IDynamicRHIModule* DynamicRHIModule = &FModuleManager::LoadModuleChecked(TEXT("NullDrv")); // Create the dynamic RHI. if ((DynamicRHIModule == 0) || !DynamicRHIModule->IsSupported()) { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("DynamicRHI", "NullDrvFailure", "NullDrv failure?")); FPlatformMisc::RequestExit(true, TEXT("InitNullRHI")); } GDynamicRHI = DynamicRHIModule->CreateRHI(); GDynamicRHI->Init(); GUsingNullRHI = true; GRHISupportsTextureStreaming = false; // Update the crash context analytics FGenericCrashContext::SetEngineData(TEXT("RHI.RHIName"), TEXT("NullRHI")); } #if PLATFORM_WINDOWS || PLATFORM_UNIX void RHIDetectAndWarnOfBadDrivers(bool bHasEditorToken) { // Don't show another prompt if we already did during this session. if (bDriverDenylistMessageShown) { return; } if (GRHIVendorId == 0) { UE_LOG(LogRHI, Log, TEXT("Skipping Driver Check, no vendor ID set.")); return; } FGPUDriverInfo DriverInfo; DriverInfo.VendorId = GRHIVendorId; DriverInfo.DeviceDescription = GRHIAdapterName; DriverInfo.ProviderName = TEXT("Unknown"); DriverInfo.InternalDriverVersion = GRHIAdapterInternalDriverVersion; DriverInfo.UserDriverVersion = GRHIAdapterUserDriverVersion; DriverInfo.DriverDate = GRHIAdapterDriverDate; DriverInfo.RHIName = GDynamicRHI ? GDynamicRHI->GetName() : FString(); FGPUDriverHelper DetectedGPUHardware(DriverInfo); // Pre-GCN GPUs usually don't support updating to latest driver // But it is unclear what is the latest version supported as it varies from card to card // So just don't complain if pre-gcn if (DriverInfo.IsValid() && !GRHIDeviceIsAMDPreGCNArchitecture) { TOptional DenyListEntry = DetectedGPUHardware.FindDriverDenyListEntry(); GRHIAdapterDriverOnDenyList = DenyListEntry.IsSet() && DenyListEntry->IsValid(); FGenericCrashContext::SetEngineData(TEXT("RHI.DriverDenylisted"), GRHIAdapterDriverOnDenyList ? TEXT("true") : TEXT("false")); if(!GRHIAdapterDriverOnDenyList) { return; } TOptional SuggestedDriver = DetectedGPUHardware.FindSuggestedDriverVersion(); const FString SuggestedDriverString = SuggestedDriver ? SuggestedDriver->SuggestedDriverVersion : FString(TEXT("Unknown")); UE_LOG(LogRHI, Warning, TEXT("Out of date driver found. Using: '%s' Suggested: '%s'"), *DriverInfo.UserDriverVersion, (SuggestedDriver ? *SuggestedDriver->SuggestedDriverVersion : TEXT("Unknown"))); TArray DeviceCanUpdateDriverList; GConfig->GetArray(TEXT("Devices"), TEXT("DeviceCanUpdateDriverList"), DeviceCanUpdateDriverList, GHardwareIni); bool bVendorHasEntries = false; bool bDeviceCanUpdateDriver = false; for (const FString& DeviceCanUpdateDriverString : DeviceCanUpdateDriverList) { const TCHAR* Line = *DeviceCanUpdateDriverString; FString VendorId; FParse::Value(Line + 1, TEXT("VendorId="), VendorId); uint32 VendorIdInt = FParse::HexNumber(*VendorId); FString DeviceId; FParse::Value(Line + 1, TEXT("DeviceId="), DeviceId); uint32 DeviceIdInt = FParse::HexNumber(*DeviceId); bVendorHasEntries |= DriverInfo.VendorId && DriverInfo.VendorId == VendorIdInt; if (DriverInfo.VendorId && GRHIDeviceId && DriverInfo.VendorId == VendorIdInt && GRHIDeviceId == DeviceIdInt) { bDeviceCanUpdateDriver = true; break; } } // Only alert users who are capable of updating their driver. Assume vendors with an empty list can always update. // The warning message can also be suppressed with r.WarnOfBadDrivers=0. bool bShowPrompt = bDeviceCanUpdateDriver || !bVendorHasEntries; bShowPrompt = bShowPrompt && !FApp::IsUnattended() && GWarnOfBadDrivers != 0; if (bShowPrompt) { // Note: we don't localize the vendor's name. FString VendorString = DriverInfo.ProviderName; FText HyperlinkText; if (DriverInfo.IsNVIDIA()) { VendorString = TEXT("NVIDIA"); HyperlinkText = NSLOCTEXT("MessageDialog", "DriverDownloadLinkNVIDIA", "https://www.nvidia.com/download/index.aspx"); } else if (DriverInfo.IsAMD()) { VendorString = TEXT("AMD"); HyperlinkText = NSLOCTEXT("MessageDialog", "DriverDownloadLinkAMD", "https://www.amd.com/en/support"); } else if (DriverInfo.IsIntel()) { VendorString = TEXT("Intel"); HyperlinkText = NSLOCTEXT("MessageDialog", "DriverDownloadLinkIntel", "https://downloadcenter.intel.com/product/80939/Graphics"); } FFormatNamedArguments Args; Args.Add(TEXT("AdapterName"), FText::FromString(DriverInfo.DeviceDescription)); Args.Add(TEXT("Vendor"), FText::FromString(VendorString)); Args.Add(TEXT("Hyperlink"), HyperlinkText); Args.Add(TEXT("InstalledVer"), FText::FromString(DriverInfo.UserDriverVersion)); // Find the best driver version to recommend. if (SuggestedDriver) { // Suggest the latest too, if not denylisted. if (!DenyListEntry->AppliesToLatestDrivers()) { Args.Add(TEXT("RecommendedVer"), FText::Format(NSLOCTEXT("MessageDialog", "SuggestedDriverOrLatest", "{0} or latest driver available"), FText::FromString(SuggestedDriver->SuggestedDriverVersion))); } else { Args.Add(TEXT("RecommendedVer"), FText::FromString(SuggestedDriver->SuggestedDriverVersion)); } } else { ensureMsgf(!DenyListEntry->AppliesToLatestDrivers(), TEXT("Latest drivers are denylisted but no recommended driver driver has been provided")); Args.Add(TEXT("RecommendedVer"), NSLOCTEXT("MessageDialog", "LatestDriver", "latest driver available")); } FText LocalizedMsg; if (DenyListEntry->RHINameConstraint) { Args.Add(TEXT("RHI"), FText::FromString(*DenyListEntry->RHINameConstraint)); LocalizedMsg = FText::Format(NSLOCTEXT("MessageDialog", "VideoCardDriverRHIIssueReport", "The installed version of the {Vendor} graphics driver has known issues in {RHI}.\nPlease install the recommended driver version or switch to a different rendering API.\n\nWould you like to visit the following URL to download the driver?\n\n{Hyperlink}\n\n{AdapterName}\nInstalled: {InstalledVer}\nRecommended: {RecommendedVer}"), Args); } else { LocalizedMsg = FText::Format(NSLOCTEXT("MessageDialog", "VideoCardDriverIssueReport", "The installed version of the {Vendor} graphics driver has known issues.\nPlease install the recommended driver version.\n\nWould you like to visit the following URL to download the driver?\n\n{Hyperlink}\n\n{AdapterName}\nInstalled: {InstalledVer}\nRecommended: {RecommendedVer}"), Args); } FText Title = NSLOCTEXT("MessageDialog", "TitleVideoCardDriverIssue", "WARNING: Known issues with graphics driver"); EAppReturnType::Type Response = FMessageDialog::Open(EAppMsgType::YesNo, LocalizedMsg, Title); if (Response == EAppReturnType::Yes) { FPlatformProcess::LaunchURL(*HyperlinkText.ToString(), nullptr, nullptr); } #if !UE_BUILD_SHIPPING if (GBadDriverWarningIsFatal) { // Force a fatal error depending on CVar. UE_LOG(LogRHI, Fatal, TEXT("Fatal crash requested when graphics drivers are out of date.\n") TEXT("To prevent this crash, please update drivers.")); } #endif bDriverDenylistMessageShown = true; } else { UE_LOG(LogRHI, Warning, TEXT("Running with bad GPU drivers but warning dialog will not be shown: bDeviceCanUpdateDriver=%d, VendorHasEntries=%d, IsUnattended=%d, r.WarnOfBadDrivers=%d"), bDeviceCanUpdateDriver, bVendorHasEntries, FApp::IsUnattended(), GWarnOfBadDrivers); } } } #elif PLATFORM_MAC void RHIDetectAndWarnOfBadDrivers(bool bHasEditorToken) { // Don't show another prompt if we already did during this session. if (bDriverDenylistMessageShown) { return; } if (!GWarnOfBadDrivers || GRHIVendorId == 0 || bHasEditorToken || FApp::IsUnattended()) { return; } if (FPlatformMisc::MacOSXVersionCompare(12, 0, 0) < 0) { // this message can be suppressed with r.WarnOfBadDrivers=0 FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *NSLOCTEXT("MessageDialog", "UpdateMacOSX_Body", "Please update to the latest version of macOS for best performance and stability.").ToString(), *NSLOCTEXT("MessageDialog", "UpdateMacOSX_Title", "Update macOS").ToString()); #if !UE_BUILD_SHIPPING if (GBadDriverWarningIsFatal) { // Force a fatal error depending on CVar UE_LOG(LogRHI, Fatal, TEXT("Fatal crash requested when graphics drivers are out of date.\n") TEXT("To prevent this crash, please update macOS.")); } #endif bDriverDenylistMessageShown = true; } } #endif // PLATFORM_WINDOWS void RHIInit(bool bHasEditorToken) { if (!GDynamicRHI) { #if RHI_ENABLE_RESOURCE_INFO FRHIResource::StartTrackingAllResources(); #endif // read in any data driven shader platform info structures we can find FGenericDataDrivenShaderPlatformInfo::Initialize(); GRHICommandList.LatchBypass(); // read commandline for bypass flag if (!FApp::CanEverRender()) { InitNullRHI(); } else { LLM_SCOPE(ELLMTag::RHIMisc); GDynamicRHI = PlatformCreateDynamicRHI(); if (GDynamicRHI) { #if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_UNIX // Get driver version. Creating GDynamicRHI is expected to set GRHIAdapterName. FGPUDriverInfo GPUDriverInfo = FPlatformMisc::GetGPUDriverInfo(GRHIAdapterName); if (GPUDriverInfo.IsValid()) { // GetGPUDriverInfo is not implemented on Linux, so it returns an invalid driver info object. However, the FVulkanDynamicRHI constructor // sets these values on that platform, so we'll still have data we can log. GRHIAdapterUserDriverVersion = GPUDriverInfo.UserDriverVersion; GRHIAdapterInternalDriverVersion = GPUDriverInfo.InternalDriverVersion; GRHIAdapterDriverDate = GPUDriverInfo.DriverDate; } UE_LOG(LogRHI, Log, TEXT("RHI Adapter Info:")); UE_LOG(LogRHI, Log, TEXT(" Name: %s"), *GRHIAdapterName); UE_LOG(LogRHI, Log, TEXT(" Driver Version: %s (internal:%s, unified:%s)"), *GRHIAdapterUserDriverVersion, *GRHIAdapterInternalDriverVersion, *GPUDriverInfo.GetUnifiedDriverVersion()); UE_LOG(LogRHI, Log, TEXT(" Driver Date: %s"), *GRHIAdapterDriverDate); RHIDetectAndWarnOfBadDrivers(bHasEditorToken); #endif GDynamicRHI->Init(); // Validation of contexts. GRHICommandList.GetImmediateCommandList().GetContext(); check(GIsRHIInitialized); // Set default GPU mask to all GPUs. This is necessary to ensure that any commands // that create and initialize resources are executed on all GPUs. Scene rendering // will restrict itself to a subset of GPUs as needed. GRHICommandList.GetImmediateCommandList().SetGPUMask(FRHIGPUMask::All()); FString FeatureLevelString; GetFeatureLevelName(GMaxRHIFeatureLevel, FeatureLevelString); if (bHasEditorToken && GMaxRHIFeatureLevel < ERHIFeatureLevel::SM5) { FString ShaderPlatformString = LegacyShaderPlatformToShaderFormat(GetFeatureLevelShaderPlatform(GMaxRHIFeatureLevel)).ToString(); FString Error = FString::Printf(TEXT("A Feature Level 5 video card is required to run the editor.\nAvailableFeatureLevel = %s, ShaderPlatform = %s"), *FeatureLevelString, *ShaderPlatformString); FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Error)); FPlatformMisc::RequestExit(true, TEXT("RHIInit")); } // Update the crash context analytics FGenericCrashContext::SetEngineData(TEXT("RHI.RHIName"), GDynamicRHI ? (GMaxRHIFeatureLevel == ERHIFeatureLevel::ES3_1 ? FString(GDynamicRHI->GetName()) + TEXT("_ES31") : GDynamicRHI->GetName()) : TEXT("Unknown")); FGenericCrashContext::SetEngineData(TEXT("RHI.AdapterName"), GRHIAdapterName); FGenericCrashContext::SetEngineData(TEXT("RHI.UserDriverVersion"), GRHIAdapterUserDriverVersion); FGenericCrashContext::SetEngineData(TEXT("RHI.InternalDriverVersion"), GRHIAdapterInternalDriverVersion); FGenericCrashContext::SetEngineData(TEXT("RHI.DriverDate"), GRHIAdapterDriverDate); FGenericCrashContext::SetEngineData(TEXT("RHI.FeatureLevel"), FeatureLevelString); FGenericCrashContext::SetEngineData(TEXT("RHI.GPUVendor"), RHIVendorIdToString()); FGenericCrashContext::SetEngineData(TEXT("RHI.DeviceId"), FString::Printf(TEXT("%04X"), GRHIDeviceId)); } #if PLATFORM_ALLOW_NULL_RHI else { // If the platform supports doing so, fall back to the NULL RHI on failure InitNullRHI(); } #endif } check(GDynamicRHI); } #if WITH_EDITOR FGenericDataDrivenShaderPlatformInfo::UpdatePreviewPlatforms(); // add an ability to override the shader platform, intended for preview platforms only FString ShaderPlatformName; if (FParse::Value(FCommandLine::Get(), TEXT("OverrideSP="), ShaderPlatformName) && !ShaderPlatformName.IsEmpty()) { EShaderPlatform OverrideShaderPlatform = FDataDrivenShaderPlatformInfo::GetShaderPlatformFromName(*ShaderPlatformName); if (OverrideShaderPlatform != SP_NumPlatforms) { // only allow to override to preview shader platform (to avoid complications), and make sure the SP matches current feature level if (FDataDrivenShaderPlatformInfo::GetIsPreviewPlatform(OverrideShaderPlatform)) { if (GetMaxSupportedFeatureLevel(OverrideShaderPlatform) == GMaxRHIFeatureLevel) { UE_LOG(LogRHI, Log, TEXT("Overriding shader platform to use from %s to %s"), *LexToString(GMaxRHIShaderPlatform, false), *LexToString(OverrideShaderPlatform, false)); GShaderPlatformForFeatureLevel[GMaxRHIFeatureLevel] = OverrideShaderPlatform; GMaxRHIShaderPlatform = OverrideShaderPlatform; } else { UE_LOG(LogRHI, Log, TEXT("Cannot override to use shaderplatform %s, its max feature level %s does not match current max feature level %s"), *LexToString(OverrideShaderPlatform, false), *LexToString(GetMaxSupportedFeatureLevel(OverrideShaderPlatform)), *LexToString(GMaxRHIFeatureLevel) ); } } else { UE_LOG(LogRHI, Log, TEXT("Cannot override to use shader platform %s, it is not a preview platform"), *LexToString(OverrideShaderPlatform, false)); } } else { UE_LOG(LogRHI, Log, TEXT("Shader platform %s is not a valid platform, cannot use it."), *LexToString(OverrideShaderPlatform, false)); } } #endif // Set CSV profiler metadata so every capture can see this // GDynamicRHI is checked above CSV_METADATA(TEXT("VerbatimRHIName"), GDynamicRHI->GetName()); // there's already an unfortunate precedent to concoct RHIName for PCD3D_ES31 and such platforms CSV_METADATA(TEXT("RHIName"), GMaxRHIFeatureLevel == ERHIFeatureLevel::ES3_1 ? *(FString(GDynamicRHI->GetName()) + TEXT("_ES31")) : GDynamicRHI->GetName()); CSV_METADATA(TEXT("RHIFeatureLevel"), *LexToString(GMaxRHIFeatureLevel)); CSV_METADATA(TEXT("ShaderPlatform"), *LexToString(GetFeatureLevelShaderPlatform(GMaxRHIFeatureLevel))); } void RHIPostInit(const TArray& InPixelFormatByteWidth) { check(GDynamicRHI); GDynamicRHI->InitPixelFormatInfo(InPixelFormatByteWidth); GDynamicRHI->PostInit(); #if PLATFORM_ANDROID // The Android HW window is locked during init to prevent it being destroyed asynch during app backgrounding // It is unlocked after the RHI is initialized as it is able to handle backgrounding. FAndroidMisc::UnlockAndroidWindow(); #endif } void RHIExit() { if (!GUsingNullRHI && GDynamicRHI != NULL) { // Clean up all cached pipelines PipelineStateCache::Shutdown(); // Flush any potential commands queued before we shut things down. FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::FlushRHIThread); // Destruct the dynamic RHI. GDynamicRHI->Shutdown(); delete GDynamicRHI; GDynamicRHI = NULL; #if RHI_ENABLE_RESOURCE_INFO FRHIResource::StopTrackingAllResources(); #endif } else if (GUsingNullRHI) { // If we are using NullRHI flush the command list here in case somethings has been added to the command list during exit calls FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources); FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::FlushRHIThread); } GRHICommandList.CleanupGraphEvents(); } FDynamicRHI::~FDynamicRHI() = default; void FDynamicRHI::RHIEndFrame_RenderThread(FRHICommandListImmediate& RHICmdList) { RHICmdList.ImmediateFlush(EImmediateFlushType::DispatchToRHIThread, ERHISubmitFlags::EndFrame); } // Default fallback; will not work for non-8-bit surfaces and it's extremely slow. void FDynamicRHI::RHIReadSurfaceData(FRHITexture* Texture, FIntRect Rect, TArray& OutData, FReadSurfaceDataFlags InFlags) { TArray TempData; RHIReadSurfaceData(Texture, Rect, TempData, InFlags); OutData.SetNumUninitialized(TempData.Num()); for (int32 Index = 0; Index < TempData.Num(); ++Index) { OutData[Index] = TempData[Index].ReinterpretAsLinear(); } } void FDynamicRHI::RHIReadSurfaceFloatData(FRHITexture* Texture, FIntRect Rect, TArray& OutData, FReadSurfaceDataFlags InFlags) { #if WITH_MGPU if (InFlags.GetGPUIndex() != 0) { unimplemented(); } else #endif { RHIReadSurfaceFloatData(Texture, Rect, OutData, InFlags.GetCubeFace(), InFlags.GetArrayIndex(), InFlags.GetMip()); } } void FDynamicRHI::RHIRead3DSurfaceFloatData(FRHITexture* Texture, FIntRect Rect, FIntPoint ZMinMax, TArray& OutData, FReadSurfaceDataFlags InFlags) { #if WITH_MGPU if (InFlags.GetGPUIndex() != 0) { unimplemented(); } else #endif { RHIRead3DSurfaceFloatData(Texture, Rect, ZMinMax, OutData); } } void FDynamicRHI::EnableIdealGPUCaptureOptions(bool bEnabled) { UE_LOG(LogRHI, Display, TEXT("Setting GPU Capture Options: %i"), bEnabled ? 1 : 0); // Draw Events if (bEnabled != (GetEmitDrawEvents() != 0)) { UE_LOG(LogRHI, Display, TEXT("Toggling draw events: %i"), bEnabled ? 1 : 0); SetEmitDrawEvents(bEnabled); } // Material Draw Events static IConsoleVariable* ShowMaterialDrawEventVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ShowMaterialDrawEvents")); if (ShowMaterialDrawEventVar) { if (bEnabled != (ShowMaterialDrawEventVar->GetInt() != 0)) { UE_LOG(LogRHI, Display, TEXT("Toggling showmaterialdrawevents: %i"), bEnabled ? 1 : 0); ShowMaterialDrawEventVar->Set(bEnabled ? -1 : 0); } } // Full pass names in RDG static IConsoleVariable* RDGEvents = IConsoleManager::Get().FindConsoleVariable(TEXT("r.RDG.Events")); if (RDGEvents) { if (bEnabled) { UE_LOG(LogRHI, Display, TEXT("Enabling full RDG events (r.RDG.Events 3)")); RDGEvents->Set(3, ECVF_SetByCode); } else { UE_LOG(LogRHI, Display, TEXT("Resetting RDG events to default (r.RDG.Events)")); RDGEvents->Unset(ECVF_SetByCode); } } } FTextureReferenceRHIRef FDynamicRHI::RHICreateTextureReference(FRHICommandListBase& RHICmdList, FRHITexture* InReferencedTexture) { FRHITexture* ReferencedTexture = InReferencedTexture ? InReferencedTexture : FRHITextureReference::GetDefaultTexture(); return new FRHITextureReference(ReferencedTexture); } void FDynamicRHI::RHIUpdateTextureReference(FRHICommandListBase& RHICmdList, FRHITextureReference* TextureRef, FRHITexture* InReferencedTexture) { // Workaround for a crash bug where FRHITextureReferences are deleted before this command is executed on the RHI thread. // Take a reference on the FRHITextureReference object to keep it alive. // @todo dev-pr - This should be refactored out when we eventually remove FRHITextureReference. TRefCountPtr Ref = TextureRef; RHICmdList.EnqueueLambda(TEXT("FDynamicRHI::RHIUpdateTextureReference"), [TextureRef = MoveTemp(Ref), InReferencedTexture](FRHICommandListBase&) { FRHITexture* ReferencedTexture = InReferencedTexture ? InReferencedTexture : FRHITextureReference::GetDefaultTexture(); TextureRef->SetReferencedTexture(ReferencedTexture); }); RHICmdList.RHIThreadFence(true); } void FDynamicRHI::RHIVirtualTextureSetFirstMipInMemory(FRHICommandListImmediate& RHICmdList, FRHITexture* TextureRHI, uint32 FirstMip) { UE_LOG(LogRHI, Fatal, TEXT("The current RHI does not implement support for virtually allocated textures.")); } void FDynamicRHI::RHIVirtualTextureSetFirstMipVisible(FRHICommandListImmediate& RHICmdList, FRHITexture* TextureRHI, uint32 FirstMip) { UE_LOG(LogRHI, Fatal, TEXT("The current RHI does not implement support for virtually allocated textures.")); } uint64 FDynamicRHI::RHIGetMinimumAlignmentForBufferBackedSRV(EPixelFormat Format) { return GPixelFormats[Format].BlockBytes; } uint64 FDynamicRHI::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; #endif // PLATFORM_SUPPORTS_MESH_SHADERS uint32 BlendState; uint32 RasterizerState; uint32 DepthStencilState; uint32 ImmutableSamplerState; uint32 DrawShadingRate : 8; uint32 PrimitiveType : 8; uint32 bDepthBounds : 1; uint32 Unused : 15; } HashKey; FMemory::Memzero(&HashKey, sizeof(FHashKey)); 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; #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; uint64 PrecachePSOHash = CityHash64((const char*)&HashKey, sizeof(FHashKey)); return PrecachePSOHash; } uint64 FDynamicRHI::RHIComputePrecachePSOHash(const FGraphicsPipelineStateInitializer& Initializer) { uint64 StatePrecachePSOHash = Initializer.StatePrecachePSOHash; if (StatePrecachePSOHash == 0) { StatePrecachePSOHash = RHIComputeStatePrecachePSOHash(Initializer); } checkf(StatePrecachePSOHash != 0, TEXT("Initializer should have a valid state precache PSO hash set when computing the full initializer PSO hash")); // All members which are not part of the state objects struct FNonStateHashKey { uint64 StatePrecachePSOHash; EPrimitiveType PrimitiveType; uint32 RenderTargetsEnabled; FGraphicsPipelineStateInitializer::TRenderTargetFormats RenderTargetFormats; FGraphicsPipelineStateInitializer::TRenderTargetFlags RenderTargetFlags; EPixelFormat DepthStencilTargetFormat; ETextureCreateFlags DepthStencilTargetFlag; ERenderTargetLoadAction DepthTargetLoadAction; ERenderTargetStoreAction DepthTargetStoreAction; ERenderTargetLoadAction StencilTargetLoadAction; ERenderTargetStoreAction StencilTargetStoreAction; FExclusiveDepthStencil DepthStencilAccess; uint16 NumSamples; ESubpassHint SubpassHint; uint8 SubpassIndex; EConservativeRasterization ConservativeRasterization; bool bDepthBounds; uint8 MultiViewCount; bool bHasFragmentDensityAttachment; EVRSShadingRate ShadingRate; } HashKey; FMemory::Memzero(&HashKey, sizeof(FNonStateHashKey)); HashKey.StatePrecachePSOHash = StatePrecachePSOHash; HashKey.PrimitiveType = Initializer.PrimitiveType; HashKey.RenderTargetsEnabled = Initializer.RenderTargetsEnabled; HashKey.RenderTargetFormats = Initializer.RenderTargetFormats; HashKey.RenderTargetFlags = Initializer.RenderTargetFlags; HashKey.DepthStencilTargetFormat = Initializer.DepthStencilTargetFormat; HashKey.DepthStencilTargetFlag = Initializer.DepthStencilTargetFlag; HashKey.DepthTargetLoadAction = Initializer.DepthTargetLoadAction; HashKey.DepthTargetStoreAction = Initializer.DepthTargetStoreAction; HashKey.StencilTargetLoadAction = Initializer.StencilTargetLoadAction; HashKey.StencilTargetStoreAction = Initializer.StencilTargetStoreAction; HashKey.DepthStencilAccess = Initializer.DepthStencilAccess; HashKey.NumSamples = Initializer.NumSamples; HashKey.SubpassHint = Initializer.SubpassHint; HashKey.SubpassIndex = Initializer.SubpassIndex; HashKey.ConservativeRasterization = Initializer.ConservativeRasterization; HashKey.bDepthBounds = Initializer.bDepthBounds; HashKey.MultiViewCount = Initializer.MultiViewCount; HashKey.bHasFragmentDensityAttachment = Initializer.bHasFragmentDensityAttachment; HashKey.ShadingRate = Initializer.ShadingRate; for (ETextureCreateFlags& Flags : HashKey.RenderTargetFlags) { Flags = Flags & FGraphicsPipelineStateInitializer::RelevantRenderTargetFlagMask; } return CityHash64((const char*)&HashKey, sizeof(FNonStateHashKey)); } bool FDynamicRHI::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.RenderTargetsEnabled != RHS.RenderTargetsEnabled || LHS.RenderTargetFormats != RHS.RenderTargetFormats || !FGraphicsPipelineStateInitializer::RelevantRenderTargetFlagsEqual(LHS.RenderTargetFlags, RHS.RenderTargetFlags) || LHS.DepthStencilTargetFormat != RHS.DepthStencilTargetFormat || !FGraphicsPipelineStateInitializer::RelevantDepthStencilFlagsEqual(LHS.DepthStencilTargetFlag, RHS.DepthStencilTargetFlag) || LHS.DepthTargetLoadAction != RHS.DepthTargetLoadAction || LHS.DepthTargetStoreAction != RHS.DepthTargetStoreAction || LHS.StencilTargetLoadAction != RHS.StencilTargetLoadAction || LHS.StencilTargetStoreAction != RHS.StencilTargetStoreAction || LHS.DepthStencilAccess != RHS.DepthStencilAccess || LHS.NumSamples != RHS.NumSamples || LHS.SubpassHint != RHS.SubpassHint || LHS.SubpassIndex != RHS.SubpassIndex || 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; } FDefaultRHIRenderQueryPool::~FDefaultRHIRenderQueryPool() { check(IsInRHIThread() || IsInRenderingThread()); checkf(AllocatedQueries == Queries.Num(), TEXT("Querypool deleted before all Queries have been released")); } FRHIPooledRenderQuery FDefaultRHIRenderQueryPool::AllocateQuery() { check(IsInParallelRenderingThread()); UE::TScopeLock Lock(QueriesMutex); if (Queries.Num() > 0) { return FRHIPooledRenderQuery(this, Queries.Pop()); } else { FRHIPooledRenderQuery Query = FRHIPooledRenderQuery(this, RHICreateRenderQuery(QueryType)); if (Query.IsValid()) { ++AllocatedQueries; } return Query; } } void FDefaultRHIRenderQueryPool::ReleaseQuery(TRefCountPtr&& Query) { check(IsInParallelRenderingThread()); checkf(Query.IsValid(), TEXT("Only release valid queries")); UE::TScopeLock Lock(QueriesMutex); Queries.Push(MoveTemp(Query)); check(!Query.IsValid()); } FRenderQueryPoolRHIRef RHICreateRenderQueryPool(ERenderQueryType QueryType, uint32 /* unused NumQueries */) { return new FDefaultRHIRenderQueryPool(QueryType); } FRHITransition* RHICreateTransition(const FRHITransitionCreateInfo& CreateInfo) { // Placement create the transition on the heap. FRHITransition* Transition = new (FRHICmdListBaseLinearAllocator::Malloc(FRHITransition::GetTotalAllocationSize(), (uint32)FRHITransition::GetAlignment())) FRHITransition(CreateInfo.SrcPipelines, CreateInfo.DstPipelines, CreateInfo.Flags); GDynamicRHI->RHICreateTransition(Transition, CreateInfo); return Transition; } void FDynamicRHI::RHICheckViewportHDRStatus(FRHIViewport* Viewport) { } void* FDynamicRHI::RHILockBufferMGPU(FRHICommandListBase& RHICmdList, FRHIBuffer* Buffer, uint32 GPUIndex, uint32 Offset, uint32 Size, EResourceLockMode LockMode) { // Fall through to single GPU case check(GPUIndex == 0); return RHILockBuffer(RHICmdList, Buffer, Offset, Size, LockMode); } void FDynamicRHI::RHIUnlockBufferMGPU(FRHICommandListBase& RHICmdList, FRHIBuffer* Buffer, uint32 GPUIndex) { // Fall through to single GPU case check(GPUIndex == 0); RHIUnlockBuffer(RHICmdList, Buffer); } // Provided for back-compat. RHI_API FShaderResourceViewInitializer::FShaderResourceViewInitializer(FRHIBuffer* InBuffer, EPixelFormat InFormat, uint32 InStartOffsetBytes, uint32 InNumElements) : FRHIViewDesc::FBufferSRV::FInitializer() , Buffer(InBuffer) { if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_ByteAddressBuffer)) { SetType(FRHIViewDesc::EBufferType::Raw); } else { SetType(FRHIViewDesc::EBufferType::Typed); SetFormat(InFormat); } SetOffsetInBytes(InStartOffsetBytes); SetNumElements(InNumElements); } // Provided for back-compat. RHI_API FShaderResourceViewInitializer::FShaderResourceViewInitializer(FRHIBuffer* InBuffer, EPixelFormat InFormat) : FRHIViewDesc::FBufferSRV::FInitializer() , Buffer(InBuffer) { if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_ByteAddressBuffer)) { SetType(FRHIViewDesc::EBufferType::Raw); } else { SetType(FRHIViewDesc::EBufferType::Typed); SetFormat(InFormat); } } // Provided for back-compat. RHI_API FShaderResourceViewInitializer::FShaderResourceViewInitializer(FRHIBuffer* InBuffer, uint32 InStartOffsetBytes, uint32 InNumElements) : FRHIViewDesc::FBufferSRV::FInitializer() , Buffer(InBuffer) { SetTypeFromBuffer(Buffer); SetOffsetInBytes(InStartOffsetBytes); SetNumElements(InNumElements); } // Provided for back-compat. RHI_API FShaderResourceViewInitializer::FShaderResourceViewInitializer(FRHIBuffer* InBuffer, FRHIRayTracingScene* InRayTracingScene, uint32 InStartOffsetBytes) : FRHIViewDesc::FBufferSRV::FInitializer() , Buffer(InBuffer) { SetTypeFromBuffer(Buffer); SetRayTracingScene(InRayTracingScene); SetOffsetInBytes(InStartOffsetBytes); } // Provided for back-compat. RHI_API FShaderResourceViewInitializer::FShaderResourceViewInitializer(FRHIBuffer* InBuffer) : FRHIViewDesc::FBufferSRV::FInitializer() , Buffer(InBuffer) { SetTypeFromBuffer(InBuffer); } void FDynamicRHI::RHIBindDebugLabelName(FRHICommandListBase& RHICmdList, FRHITexture* Texture, const TCHAR* Name) { #if RHI_USE_RESOURCE_DEBUG_NAME Texture->SetName(Name); #endif } void FDynamicRHI::RHIBindDebugLabelName(FRHICommandListBase& RHICmdList, FRHIBuffer* Buffer, const TCHAR* Name) { #if RHI_USE_RESOURCE_DEBUG_NAME Buffer->SetName(Name); #endif } void FDynamicRHI::RHIBindDebugLabelName(FRHICommandListBase& RHICmdList, FRHIUnorderedAccessView* UnorderedAccessViewRHI, const TCHAR* Name) { #if RHI_USE_RESOURCE_DEBUG_NAME if (UnorderedAccessViewRHI->IsTexture()) { RHICmdList.BindDebugLabelName(UnorderedAccessViewRHI->GetTexture(), Name); } else { RHICmdList.BindDebugLabelName(UnorderedAccessViewRHI->GetBuffer(), Name); } #endif }