Files
UnrealEngine/Engine/Source/Runtime/RHI/Private/Windows/WindowsDynamicRHI.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1288 lines
42 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RHI.h"
#include "Modules/ModuleManager.h"
#include "Misc/App.h"
#include "Misc/CommandLine.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/MessageDialog.h"
#include "Containers/StaticArray.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Windows/WindowsPlatformApplicationMisc.h"
THIRD_PARTY_INCLUDES_START
#include "Windows/AllowWindowsPlatformTypes.h"
#include <dxgi.h>
#include "Windows/HideWindowsPlatformTypes.h"
THIRD_PARTY_INCLUDES_END
#define LOCTEXT_NAMESPACE "WindowsDynamicRHI"
bool GDynamicRHIFailedToInitializeAdvancedPlatform = false;
static const TCHAR* GLoadedRHIModuleName;
enum class EWindowsRHI
{
D3D11,
D3D12,
Vulkan,
OpenGL,
Imported,
count
};
static constexpr int32 EWindowsRHICount = static_cast<int32>(EWindowsRHI::count);
struct FOverrideRHI
{
FString ModuleName;
FString RHIName;
ERHIFeatureLevel::Type FeatureLevel;
};
static FOverrideRHI* GetOverrideRHI()
{
static bool bHasChecked = false;
static FOverrideRHI* Override = nullptr;
if (bHasChecked)
{
return Override;
}
bHasChecked = true;
#if ALLOW_OTHER_PLATFORM_CONFIG
FString OverrideRHIPlatformName;
if (FParse::Value(FCommandLine::Get(), TEXT("PlatformRHI="), OverrideRHIPlatformName))
{
Override = new FOverrideRHI;
FConfigCacheIni* Config = FConfigCacheIni::ForPlatform(*OverrideRHIPlatformName);
Config->GetString(TEXT("ImportedRHI"), TEXT("ModuleName"), Override->ModuleName, GEngineIni);
Config->GetString(TEXT("ImportedRHI"), TEXT("ModuleName"), Override->RHIName, GEngineIni);
Config->GetString(TEXT("ImportedRHI"), TEXT("RHIName"), Override->RHIName, GEngineIni);
FString FeatureLevel;
Config->GetString(TEXT("ImportedRHI"), TEXT("FeatureLevel"), FeatureLevel, GEngineIni);
GetFeatureLevelFromName(FeatureLevel, Override->FeatureLevel);
}
#endif
return Override;
}
inline const TCHAR* GetLogName(EWindowsRHI InWindowsRHI)
{
switch (InWindowsRHI)
{
case EWindowsRHI::D3D11: return TEXT("D3D11");
case EWindowsRHI::D3D12: return TEXT("D3D12");
case EWindowsRHI::Vulkan: return TEXT("Vulkan");
case EWindowsRHI::OpenGL: return TEXT("OpenGL");
case EWindowsRHI::Imported: return *GetOverrideRHI()->RHIName;
default: return TEXT("<unknown>");
}
}
inline FText GetTextName(EWindowsRHI InWindowsRHI)
{
switch (InWindowsRHI)
{
case EWindowsRHI::D3D11: return LOCTEXT("D3D11", "DirectX 11");
case EWindowsRHI::D3D12: return LOCTEXT("D3D12", "DirectX 12");
case EWindowsRHI::Vulkan: return LOCTEXT("Vulkan", "Vulkan");
case EWindowsRHI::OpenGL: return LOCTEXT("OpenGL", "OpenGL");
case EWindowsRHI::Imported: return FText::FromString(GetOverrideRHI()->RHIName);
default: return LOCTEXT("UnknownRHI", "<Unknown>");
}
}
inline const TCHAR* GetLogName(ERHIFeatureLevel::Type InFeatureLevel)
{
switch (InFeatureLevel)
{
case ERHIFeatureLevel::ES3_1: return TEXT("ES3_1");
case ERHIFeatureLevel::SM5: return TEXT("SM5");
case ERHIFeatureLevel::SM6: return TEXT("SM6");
default: return TEXT("<unknown>");
}
}
inline FText GetTextName(ERHIFeatureLevel::Type InFeatureLevel)
{
switch (InFeatureLevel)
{
case ERHIFeatureLevel::ES3_1: return LOCTEXT("FeatureLevelES31", "ES3_1");
case ERHIFeatureLevel::SM5: return LOCTEXT("FeatureLevelSM4", "SM5");
case ERHIFeatureLevel::SM6: return LOCTEXT("FeatureLevelSM6", "SM6");
default: return LOCTEXT("FeatureLevelUnknown", "<unknown>");
}
}
const EWindowsRHI GRHISearchOrder[] =
{
EWindowsRHI::D3D12,
EWindowsRHI::D3D11,
EWindowsRHI::Vulkan,
EWindowsRHI::OpenGL,
};
static TAutoConsoleVariable<bool> CVarIgnorePerformanceModeCheck(
TEXT("r.IgnorePerformanceModeCheck"),
false,
TEXT("Ignore performance mode check"),
ECVF_RenderThreadSafe
);
static const TCHAR* ModuleNameFromWindowsRHI(EWindowsRHI InWindowsRHI)
{
switch (InWindowsRHI)
{
default: check(false);
case EWindowsRHI::D3D11: return TEXT("D3D11RHI");
case EWindowsRHI::D3D12: return TEXT("D3D12RHI");
case EWindowsRHI::Vulkan: return TEXT("VulkanRHI");
case EWindowsRHI::OpenGL: return TEXT("OpenGLDrv");
case EWindowsRHI::Imported: return *GetOverrideRHI()->ModuleName;
}
}
static const TCHAR* GetRHINameFromWindowsRHI(EWindowsRHI InWindowsRHI)
{
switch (InWindowsRHI)
{
default: check(false);
case EWindowsRHI::D3D11: return TEXT("DirectX 11");
case EWindowsRHI::D3D12: return TEXT("DirectX 12");
case EWindowsRHI::Vulkan: return TEXT("Vulkan");
case EWindowsRHI::OpenGL: return TEXT("OpenGL");
case EWindowsRHI::Imported: return *GetOverrideRHI()->RHIName;
}
}
static FString GetRHINameFromWindowsRHI(EWindowsRHI InWindowsRHI, ERHIFeatureLevel::Type InFeatureLevel)
{
switch (InWindowsRHI)
{
case EWindowsRHI::D3D12:
default:
{
FString FeatureLevelName;
GetFeatureLevelName(InFeatureLevel, FeatureLevelName);
return FString::Printf(TEXT("%s (%s)"), GetLogName(InWindowsRHI), *FeatureLevelName);
}
case EWindowsRHI::D3D11: return TEXT("DirectX 11");
case EWindowsRHI::Vulkan:
{
FString FeatureLevelName;
GetFeatureLevelName(InFeatureLevel, FeatureLevelName);
return FString::Printf(TEXT("Vulkan (%s)"), *FeatureLevelName);
}
case EWindowsRHI::OpenGL: return TEXT("OpenGL");
}
}
static const TCHAR* GetES31ModuleName(ERHIInterfaceType InterfaceType)
{
switch (InterfaceType)
{
default: check(false);
case ERHIInterfaceType::D3D11: return TEXT("ES31_D3D11RHI");
case ERHIInterfaceType::D3D12: return TEXT("ES31_D3D12RHI");
case ERHIInterfaceType::Vulkan: return TEXT("ES31_VulkanRHI");
case ERHIInterfaceType::OpenGL: return TEXT("ES31_OpenGLDrv");
}
}
static const TCHAR* GetES31ModuleNameByString(const TCHAR* RHIModuleName)
{
if (FCString::Strcmp(RHIModuleName, ModuleNameFromWindowsRHI(EWindowsRHI::D3D11)) == 0)
{
return GetES31ModuleName(ERHIInterfaceType::D3D11);
}
if (FCString::Strcmp(RHIModuleName, ModuleNameFromWindowsRHI(EWindowsRHI::D3D12)) == 0)
{
return GetES31ModuleName(ERHIInterfaceType::D3D12);
}
if (FCString::Strcmp(RHIModuleName, ModuleNameFromWindowsRHI(EWindowsRHI::Vulkan)) == 0)
{
return GetES31ModuleName(ERHIInterfaceType::Vulkan);
}
if (FCString::Strcmp(RHIModuleName, ModuleNameFromWindowsRHI(EWindowsRHI::OpenGL)) == 0)
{
return GetES31ModuleName(ERHIInterfaceType::OpenGL);
}
check(false);
return RHIModuleName;
}
static ERHIFeatureLevel::Type GetDefaultFeatureLevelForRHI(EWindowsRHI InWindowsRHI)
{
switch (InWindowsRHI)
{
case EWindowsRHI::D3D11: return ERHIFeatureLevel::SM5;
case EWindowsRHI::D3D12: return ERHIFeatureLevel::SM5;
case EWindowsRHI::Vulkan: return ERHIFeatureLevel::SM5;
case EWindowsRHI::OpenGL: return ERHIFeatureLevel::ES3_1;
case EWindowsRHI::Imported: return GetOverrideRHI()->FeatureLevel;
default: check(false); return ERHIFeatureLevel::SM5;
}
}
struct FWindowsRHIConfig
{
TArray<EShaderPlatform> ShaderPlatforms;
TArray<ERHIFeatureLevel::Type> FeatureLevels;
};
struct FParsedWindowsDynamicRHIConfig
{
TOptional<EWindowsRHI> DefaultRHI{};
TStaticArray<FWindowsRHIConfig, EWindowsRHICount> RHIConfigs;
bool IsEmpty() const;
bool IsRHISupported(EWindowsRHI InWindowsRHI) const;
TOptional<ERHIFeatureLevel::Type> GetHighestSupportedFeatureLevel(EWindowsRHI InWindowsRHI) const;
TOptional<ERHIFeatureLevel::Type> GetNextHighestTargetedFeatureLevel(EWindowsRHI InWindowsRHI, ERHIFeatureLevel::Type InFeatureLevel) const;
bool IsFeatureLevelTargeted(EWindowsRHI InWindowsRHI, ERHIFeatureLevel::Type InFeatureLevel) const;
TOptional<EWindowsRHI> GetFirstRHIWithFeatureLevelSupport(ERHIFeatureLevel::Type InFeatureLevel) const;
void MergeConfig(EWindowsRHI InWindowsRHI, const FWindowsRHIConfig& Other);
};
bool FParsedWindowsDynamicRHIConfig::IsEmpty() const
{
for (const FWindowsRHIConfig& Config : RHIConfigs)
{
if (!Config.ShaderPlatforms.IsEmpty())
{
return false;
}
}
return true;
}
bool FParsedWindowsDynamicRHIConfig::IsRHISupported(EWindowsRHI InWindowsRHI) const
{
// If we don't require cooked data, then we should be able to support this RHI at any feature level.
if (!FPlatformProperties::RequiresCookedData())
{
return true;
}
return !RHIConfigs[(int32)InWindowsRHI].ShaderPlatforms.IsEmpty();
}
TOptional<ERHIFeatureLevel::Type> FParsedWindowsDynamicRHIConfig::GetHighestSupportedFeatureLevel(EWindowsRHI InWindowsRHI) const
{
const TArray<ERHIFeatureLevel::Type>& FeatureLevels = RHIConfigs[(int32)InWindowsRHI].FeatureLevels;
if (FeatureLevels.Num() == 0)
{
return TOptional<ERHIFeatureLevel::Type>();
}
ERHIFeatureLevel::Type MaxFeatureLevel = (ERHIFeatureLevel::Type)0;
for (ERHIFeatureLevel::Type SupportedFeatureLevel : FeatureLevels)
{
MaxFeatureLevel = std::max(MaxFeatureLevel, SupportedFeatureLevel);
}
return MaxFeatureLevel;
}
TOptional<ERHIFeatureLevel::Type> FParsedWindowsDynamicRHIConfig::GetNextHighestTargetedFeatureLevel(EWindowsRHI InWindowsRHI, ERHIFeatureLevel::Type InFeatureLevel) const
{
TArray<ERHIFeatureLevel::Type> LowerFeatureLevels(RHIConfigs[(int32)InWindowsRHI].FeatureLevels);
LowerFeatureLevels.RemoveAll([InFeatureLevel](ERHIFeatureLevel::Type OtherFeatureLevel) { return OtherFeatureLevel >= InFeatureLevel; });
if (LowerFeatureLevels.Num())
{
ERHIFeatureLevel::Type MaxFeatureLevel = (ERHIFeatureLevel::Type)0;
for (ERHIFeatureLevel::Type SupportedFeatureLevel : LowerFeatureLevels)
{
MaxFeatureLevel = std::max(MaxFeatureLevel, SupportedFeatureLevel);
}
return MaxFeatureLevel;
}
return TOptional<ERHIFeatureLevel::Type>();
}
bool FParsedWindowsDynamicRHIConfig::IsFeatureLevelTargeted(EWindowsRHI InWindowsRHI, ERHIFeatureLevel::Type InFeatureLevel) const
{
for (ERHIFeatureLevel::Type SupportedFeatureLevel : RHIConfigs[(int32)InWindowsRHI].FeatureLevels)
{
if (SupportedFeatureLevel == InFeatureLevel)
{
return true;
}
}
return false;
}
TOptional<EWindowsRHI> FParsedWindowsDynamicRHIConfig::GetFirstRHIWithFeatureLevelSupport(ERHIFeatureLevel::Type InFeatureLevel) const
{
for (EWindowsRHI WindowsRHI : GRHISearchOrder)
{
if (IsFeatureLevelTargeted(WindowsRHI, InFeatureLevel))
{
return WindowsRHI;
}
}
return {};
}
void FParsedWindowsDynamicRHIConfig::MergeConfig(EWindowsRHI InWindowsRHI, const FWindowsRHIConfig& Other)
{
FWindowsRHIConfig& ExistingConfig = RHIConfigs[(int32)InWindowsRHI];
for (EShaderPlatform ShaderPlatform : Other.ShaderPlatforms)
{
ExistingConfig.ShaderPlatforms.AddUnique(ShaderPlatform);
}
for (ERHIFeatureLevel::Type FeatureLevel : Other.FeatureLevels)
{
ExistingConfig.FeatureLevels.AddUnique(FeatureLevel);
}
}
TOptional<EWindowsRHI> ParseDefaultWindowsRHI()
{
TOptional<EWindowsRHI> DefaultRHI{};
FString DefaultGraphicsRHI;
if (GConfig->GetString(TEXT("/Script/WindowsTargetPlatform.WindowsTargetSettings"), TEXT("DefaultGraphicsRHI"), DefaultGraphicsRHI, GEngineIni))
{
const FString NAME_DX11(TEXT("DefaultGraphicsRHI_DX11"));
const FString NAME_DX12(TEXT("DefaultGraphicsRHI_DX12"));
const FString NAME_VULKAN(TEXT("DefaultGraphicsRHI_Vulkan"));
if (DefaultGraphicsRHI == NAME_DX11)
{
DefaultRHI = EWindowsRHI::D3D11;
}
else if (DefaultGraphicsRHI == NAME_DX12)
{
DefaultRHI = EWindowsRHI::D3D12;
}
else if (DefaultGraphicsRHI == NAME_VULKAN)
{
DefaultRHI = EWindowsRHI::Vulkan;
}
else if (DefaultGraphicsRHI != TEXT("DefaultGraphicsRHI_Default"))
{
UE_LOG(LogRHI, Error, TEXT("Unrecognized setting '%s' for DefaultGraphicsRHI"), *DefaultGraphicsRHI);
}
}
return DefaultRHI;
}
static TArray<EShaderPlatform> ParseShaderPlatformsConfig(const TCHAR* InSettingName)
{
TArray<FString> TargetedShaderFormats;
GConfig->GetArray(TEXT("/Script/WindowsTargetPlatform.WindowsTargetSettings"), InSettingName, TargetedShaderFormats, GEngineIni);
TArray<EShaderPlatform> ShaderPlatforms;
ShaderPlatforms.Reserve(TargetedShaderFormats.Num());
for (const FString& ShaderFormat : TargetedShaderFormats)
{
ShaderPlatforms.AddUnique(ShaderFormatToLegacyShaderPlatform(FName(*ShaderFormat)));
}
ShaderPlatforms.RemoveAll(
[](EShaderPlatform ShaderPlatform)
{
return GIsEditor && (FDataDrivenShaderPlatformInfo::GetMaxFeatureLevel(ShaderPlatform) < ERHIFeatureLevel::SM5);
});
return ShaderPlatforms;
}
static TArray<ERHIFeatureLevel::Type> FeatureLevelsFromShaderPlatforms(const TArray<EShaderPlatform>& InShaderPlatforms)
{
TArray<ERHIFeatureLevel::Type> FeatureLevels;
FeatureLevels.Reserve(InShaderPlatforms.Num());
for (EShaderPlatform ShaderPlatform : InShaderPlatforms)
{
FeatureLevels.AddUnique(FDataDrivenShaderPlatformInfo::GetMaxFeatureLevel(ShaderPlatform));
}
return FeatureLevels;
}
static FWindowsRHIConfig ParseWindowsRHIConfig(const TCHAR* ShaderFormatsSettingName)
{
FWindowsRHIConfig Config;
Config.ShaderPlatforms = ParseShaderPlatformsConfig(ShaderFormatsSettingName);
Config.FeatureLevels = FeatureLevelsFromShaderPlatforms(Config.ShaderPlatforms);
return Config;
}
FParsedWindowsDynamicRHIConfig ParseWindowsDynamicRHIConfig()
{
FParsedWindowsDynamicRHIConfig Config;
Config.DefaultRHI = ParseDefaultWindowsRHI();
Config.RHIConfigs[(int32)EWindowsRHI::D3D11] = ParseWindowsRHIConfig(TEXT("D3D11TargetedShaderFormats"));
Config.RHIConfigs[(int32)EWindowsRHI::D3D12] = ParseWindowsRHIConfig(TEXT("D3D12TargetedShaderFormats"));
Config.RHIConfigs[(int32)EWindowsRHI::Vulkan] = ParseWindowsRHIConfig(TEXT("VulkanTargetedShaderFormats"));
// Only add OpenGL support to non-client programs.
if (!FPlatformProperties::RequiresCookedData())
{
Config.RHIConfigs[(int32)EWindowsRHI::OpenGL].ShaderPlatforms.Add(SP_OPENGL_PCES3_1);
Config.RHIConfigs[(int32)EWindowsRHI::OpenGL].FeatureLevels.Add(ERHIFeatureLevel::ES3_1);
}
if (FWindowsRHIConfig DeprecatedConfig = ParseWindowsRHIConfig(TEXT("TargetedRHIs")); !DeprecatedConfig.ShaderPlatforms.IsEmpty())
{
// Since we don't have context here, we have to add this old config setting to every potential RHI.
Config.MergeConfig(EWindowsRHI::D3D11, DeprecatedConfig);
Config.MergeConfig(EWindowsRHI::D3D12, DeprecatedConfig);
Config.MergeConfig(EWindowsRHI::Vulkan, DeprecatedConfig);
Config.MergeConfig(EWindowsRHI::OpenGL, DeprecatedConfig);
}
return Config;
}
struct FWindowsGPUInfo
{
uint32 VendorId = 0;
uint32 DeviceId = 0;
uint64 DedicatedVideoMemory = 0;
FString AdapterName;
};
static void GetGPUInfo(FWindowsGPUInfo& Info)
{
DXGI_ADAPTER_DESC BestDesc = {};
TRefCountPtr<IDXGIFactory1> DXGIFactory1;
if (SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(DXGIFactory1.GetInitReference()))))
{
TRefCountPtr<IDXGIAdapter> TempAdapter;
for (uint32 AdapterIndex = 0; DXGIFactory1->EnumAdapters(AdapterIndex, TempAdapter.GetInitReference()) != DXGI_ERROR_NOT_FOUND; ++AdapterIndex)
{
if (TempAdapter)
{
DXGI_ADAPTER_DESC Desc;
TempAdapter->GetDesc(&Desc);
if (Desc.DedicatedVideoMemory > BestDesc.DedicatedVideoMemory || AdapterIndex == 0)
{
BestDesc = Desc;
}
}
}
}
Info.VendorId = BestDesc.VendorId;
Info.DeviceId = BestDesc.DeviceId;
Info.DedicatedVideoMemory = BestDesc.DedicatedVideoMemory;
Info.AdapterName = BestDesc.Description;
if (Info.AdapterName.IsEmpty())
{
Info.AdapterName = FPlatformMisc::GetPrimaryGPUBrand();
}
}
static bool IsGPUInHardwareListForD3D11_ES31(const FWindowsGPUInfo& BestGPUInfo, const TCHAR* const ConfigKey)
{
TArray<FString> DeviceRHIList;
GConfig->GetArray(TEXT("Devices"), ConfigKey, DeviceRHIList, GHardwareIni);
const FString& GPUBrand = BestGPUInfo.AdapterName;
for (const FString& DeviceRHIString : DeviceRHIList)
{
const TCHAR* Line = *DeviceRHIString;
ensure(Line[0] == TCHAR('('));
FString RHIName;
FParse::Value(Line + 1, TEXT("RHI="), RHIName);
FString DeviceName;
FParse::Value(Line + 1, TEXT("DeviceName="), DeviceName);
if (RHIName.Compare("D3D11_ES31", ESearchCase::IgnoreCase) == 0 && GPUBrand.Compare(DeviceName, ESearchCase::IgnoreCase) == 0)
{
UE_LOG(LogRHI, Log, TEXT("%s: Found RHIName %s with DeviceName %s"), ConfigKey, *RHIName, *DeviceName);
return true;
}
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);
if (BestGPUInfo.VendorId && BestGPUInfo.DeviceId &&
BestGPUInfo.VendorId == VendorIdInt && BestGPUInfo.DeviceId == DeviceIdInt &&
RHIName.Compare("D3D11_ES31", ESearchCase::IgnoreCase) == 0)
{
UE_LOG(LogRHI, Log, TEXT("%s: Found RHIName %s with DeviceName %s, VendorId %s, and DeviceId %s"), ConfigKey, *RHIName, *DeviceName, *VendorId, *DeviceId);
return true;
}
}
return false;
}
static bool IsForcedToD3D11_ES31()
{
bool bRequireD3D11ES31 = false;
static TOptional<bool> ForceD3D11_ES31;
if (ForceD3D11_ES31.IsSet())
{
bRequireD3D11ES31 = ForceD3D11_ES31.GetValue();
}
else
{
FWindowsGPUInfo BestGPUInfo;
GetGPUInfo(BestGPUInfo);
bRequireD3D11ES31 = IsGPUInHardwareListForD3D11_ES31(BestGPUInfo, TEXT("DeviceForceRHIList"));
if (bRequireD3D11ES31)
{
UE_LOG(LogRHI, Log, TEXT("Forced feature level to D3D11 %s"), GetLogName(ERHIFeatureLevel::ES3_1));
}
ForceD3D11_ES31 = bRequireD3D11ES31;
}
return bRequireD3D11ES31;
}
// Whether to default to Performance Mode on low-end machines (ES31 feature level).
// Looks at a few configuration options that control thresholds to enable this as the default.
static bool ShouldDefaultToPerformanceMode(bool& bOutForceD3D11)
{
static TOptional<bool> ForceES31;
static bool bForceD3D11 = false;
if (ForceES31.IsSet())
{
bOutForceD3D11 = bForceD3D11;
return ForceES31.GetValue();
}
FWindowsGPUInfo BestGPUInfo;
GetGPUInfo(BestGPUInfo);
if (IsGPUInHardwareListForD3D11_ES31(BestGPUInfo, TEXT("DeviceDefaultRHIList")))
{
UE_LOG(LogRHI, Log, TEXT("Default feature level to %s"), GetLogName(ERHIFeatureLevel::ES3_1));
ForceES31 = true;
bForceD3D11 = true;
bOutForceD3D11 = true;
return true;
}
// Force Performance mode for laptops. We don't have an explicit way to check for laptops
// so we use whether we're running on battery as a proxy.
bool bIsLaptop = FPlatformMisc::IsRunningOnBattery();
if (GConfig->GetBoolOrDefault(TEXT("PerformanceMode"), TEXT("bDefaultOnLaptops"), false, GEngineIni) && bIsLaptop)
{
UE_LOG(LogRHI, Log, TEXT("Running on laptop: default feature level to %s"), GetLogName(ERHIFeatureLevel::ES3_1));
ForceES31 = true;
return true;
}
// Force Performance mode for machines with too few cores including hyperthreads
int MinCoreCount = 0;
if (GConfig->GetInt(TEXT("PerformanceMode"), TEXT("MinCoreCount"), MinCoreCount, GEngineIni) && FPlatformMisc::NumberOfCoresIncludingHyperthreads() < MinCoreCount)
{
UE_LOG(LogRHI, Log, TEXT("MinCoreCount found (%d) and user core count (%d): default feature level to %s"), MinCoreCount, FPlatformMisc::NumberOfCoresIncludingHyperthreads(), GetLogName(ERHIFeatureLevel::ES3_1));
ForceES31 = true;
return true;
}
FString MinMemorySizeBucketString;
FString MinIntegratedMemorySizeBucketString;
if (GConfig->GetString(TEXT("PerformanceMode"), TEXT("MinMemorySizeBucket"), MinMemorySizeBucketString, GEngineIni) && GConfig->GetString(TEXT("PerformanceMode"), TEXT("MinIntegratedMemorySizeBucket"), MinIntegratedMemorySizeBucketString, GEngineIni))
{
for (int EnumIndex = int(EPlatformMemorySizeBucket::Largest); EnumIndex <= int(EPlatformMemorySizeBucket::Tiniest); EnumIndex++)
{
const TCHAR* BucketString = LexToString(EPlatformMemorySizeBucket(EnumIndex));
// Force Performance mode for machines with too little memory
if (MinMemorySizeBucketString == BucketString)
{
if (FPlatformMemory::GetMemorySizeBucket() >= EPlatformMemorySizeBucket(EnumIndex))
{
UE_LOG(LogRHI, Log, TEXT("MinMemorySizeBucket found (%s) and user memory (%d): default feature level to %s"), *MinMemorySizeBucketString, int32(FPlatformMemory::GetMemorySizeBucket()), GetLogName(ERHIFeatureLevel::ES3_1));
ForceES31 = true;
return true;
}
}
// Force Performance mode for machines with too little memory when shared with the GPU
if (MinIntegratedMemorySizeBucketString == BucketString)
{
const int MIN_GPU_MEMORY = 512 * 1024 * 1024;
if (FPlatformMemory::GetMemorySizeBucket() >= EPlatformMemorySizeBucket(EnumIndex) && BestGPUInfo.DedicatedVideoMemory < MIN_GPU_MEMORY)
{
UE_LOG(LogRHI, Log, TEXT("MinMemorySizeBucket found (%s) and user memory (%d): default feature level to %s"), *MinMemorySizeBucketString, int32(FPlatformMemory::GetMemorySizeBucket()), GetLogName(ERHIFeatureLevel::ES3_1));
ForceES31 = true;
return true;
}
}
}
}
ForceES31 = false;
return false;
}
static TOptional<ERHIFeatureLevel::Type> GetPreferredFeatureLevel(EWindowsRHI ChosenRHI, const FParsedWindowsDynamicRHIConfig& Config, bool& bOutForceD3D11)
{
bOutForceD3D11 = false;
TOptional<ERHIFeatureLevel::Type> PreferredFeatureLevel;
if (!GIsEditor)
{
bool bSetD3D11_ES31 = IsForcedToD3D11_ES31();
if (bSetD3D11_ES31)
{
bOutForceD3D11 = true;
PreferredFeatureLevel = ERHIFeatureLevel::ES3_1;
}
else
{
FString PreferredFeatureLevelName;
if (GConfig->GetString(TEXT("D3DRHIPreference"), TEXT("PreferredFeatureLevel"), PreferredFeatureLevelName, GGameUserSettingsIni))
{
UE_LOG(LogRHI, Log, TEXT("Found D3DRHIPreference PreferredFeatureLevel: %s"), *PreferredFeatureLevelName);
if (PreferredFeatureLevelName == TEXT("sm5"))
{
PreferredFeatureLevel = ERHIFeatureLevel::SM5;
}
else if (PreferredFeatureLevelName == TEXT("sm6"))
{
PreferredFeatureLevel = ERHIFeatureLevel::SM6;
}
else if (PreferredFeatureLevelName == TEXT("es31"))
{
PreferredFeatureLevel = ERHIFeatureLevel::ES3_1;
}
else
{
UE_LOG(LogRHI, Error, TEXT("unknown feature level name \"%s\" in game user settings, using default"), *PreferredFeatureLevelName);
}
}
else if (Config.IsFeatureLevelTargeted(ChosenRHI, ERHIFeatureLevel::ES3_1))
{
bool bPreferFeatureLevelES31 = false;
bool bFoundPreference = GConfig->GetBool(TEXT("D3DRHIPreference"), TEXT("bPreferFeatureLevelES31"), bPreferFeatureLevelES31, GGameUserSettingsIni);
if (bFoundPreference)
{
UE_LOG(LogRHI, Log, TEXT("Found D3DRHIPreference bPreferFeatureLevelES31: %s"), bPreferFeatureLevelES31 ? TEXT("true") : TEXT("false"));
}
// Force low-spec users into performance mode but respect their choice once they have set a preference
bool bDefaultES31 = false;
if (!bFoundPreference && !CVarIgnorePerformanceModeCheck.GetValueOnAnyThread())
{
bDefaultES31 = ShouldDefaultToPerformanceMode(bOutForceD3D11);
}
if (bPreferFeatureLevelES31 || bDefaultES31)
{
bSetD3D11_ES31 = !bFoundPreference;
PreferredFeatureLevel = ERHIFeatureLevel::ES3_1;
}
}
}
if (bSetD3D11_ES31)
{
GConfig->SetBool(TEXT("D3DRHIPreference"), TEXT("bPreferFeatureLevelES31"), true, GGameUserSettingsIni);
if (bOutForceD3D11)
{
GConfig->SetString(TEXT("D3DRHIPreference"), TEXT("PreferredRHI"), TEXT("dx11"), GGameUserSettingsIni);
}
}
}
return PreferredFeatureLevel;
}
static bool ShouldDevicePreferSM5(uint32 DeviceId)
{
uint32 SM5PreferedDeviceIds[] =
{
0x1B80, // "NVIDIA GeForce GTX 1080"
0x1B81, // "NVIDIA GeForce GTX 1070"
0x1B82, // "NVIDIA GeForce GTX 1070 Ti"
0x1B83, // "NVIDIA GeForce GTX 1060 6GB"
0x1B84, // "NVIDIA GeForce GTX 1060 3GB"
0x1C01, // "NVIDIA GeForce GTX 1050 Ti"
0x1C02, // "NVIDIA GeForce GTX 1060 3GB"
0x1C03, // "NVIDIA GeForce GTX 1060 6GB"
0x1C04, // "NVIDIA GeForce GTX 1060 5GB"
0x1C06, // "NVIDIA GeForce GTX 1060 6GB"
0x1C08, // "NVIDIA GeForce GTX 1050"
0x1C81, // "NVIDIA GeForce GTX 1050"
0x1C82, // "NVIDIA GeForce GTX 1050 Ti"
0x1C83, // "NVIDIA GeForce GTX 1050"
0x1B06, // "NVIDIA GeForce GTX 1080 Ti"
};
for (int Index = 0; Index < UE_ARRAY_COUNT(SM5PreferedDeviceIds); ++Index)
{
if (DeviceId == SM5PreferedDeviceIds[Index])
{
return true;
}
}
return false;
}
// Whether a SM6 capable device should always default to SM6
// regardless of other heuristics suggesting otherwise.
static bool ShouldDevicePreferSM6(uint32 SM6CapableDeviceId)
{
TArray<FString> SM6PreferredDeviceIds;
GConfig->GetArray(TEXT("D3D12_SM6"), TEXT("SM6PreferredGPUDeviceIDs"), SM6PreferredDeviceIds, GEngineIni);
for (const FString& PreferredDeviceID : SM6PreferredDeviceIds)
{
if (SM6CapableDeviceId == FCString::Strtoi(*PreferredDeviceID, nullptr, 10) ||
SM6CapableDeviceId == FCString::Strtoi(*PreferredDeviceID, nullptr, 16))
{
return true;
}
}
return false;
}
static bool IsRHIAllowedAsDefault(EWindowsRHI InRHI, ERHIFeatureLevel::Type InFeatureLevel)
{
static const FWindowsPlatformApplicationMisc::FGPUInfo BestGPUInfo = FWindowsPlatformApplicationMisc::GetBestGPUInfo();
if (InRHI == EWindowsRHI::D3D12 && InFeatureLevel == ERHIFeatureLevel::SM6)
{
if (ShouldDevicePreferSM6(BestGPUInfo.DeviceId))
{
return true;
}
}
bool bAllowed = true;
if (InRHI == EWindowsRHI::D3D12 && InFeatureLevel >= ERHIFeatureLevel::SM6)
{
int32 MinDedicatedMemoryMB = 0;
if (GConfig->GetInt(TEXT("D3D12_SM6"), TEXT("MinDedicatedMemory"), MinDedicatedMemoryMB, GEngineIni))
{
const uint64 MinDedicatedMemory = static_cast<uint64>(MinDedicatedMemoryMB) << 20;
bAllowed &= BestGPUInfo.DedicatedVideoMemory >= MinDedicatedMemory;
}
bool bUseSM5PreferredGPUList = true;
GConfig->GetBool(TEXT("D3D12_SM6"), TEXT("bUseSM5PreferredGPUList"), bUseSM5PreferredGPUList, GEngineIni);
if (bUseSM5PreferredGPUList)
{
bAllowed &= !ShouldDevicePreferSM5(BestGPUInfo.DeviceId);
}
}
return bAllowed;
}
static TOptional<ERHIFeatureLevel::Type> ChooseDefaultFeatureLevel(EWindowsRHI InRHI, const FParsedWindowsDynamicRHIConfig& Config)
{
TOptional<ERHIFeatureLevel::Type> HighestFL = Config.GetHighestSupportedFeatureLevel(InRHI);
while (HighestFL)
{
if (IsRHIAllowedAsDefault(InRHI, HighestFL.GetValue()))
{
return HighestFL;
}
HighestFL = Config.GetNextHighestTargetedFeatureLevel(InRHI, HighestFL.GetValue());
}
return TOptional<ERHIFeatureLevel::Type>();
}
// Choose the default RHI using this search order:
// * DefaultGraphicsRHI if specified;
// * The first supported RHI from TargetedRHIs, enumerated according to GRHISearchOrder;
// * D3D11, if all else fails.
static EWindowsRHI ChooseDefaultRHI(const FParsedWindowsDynamicRHIConfig& Config)
{
// Default graphics RHI is the main project setting that governs the choice, so it takes the priority
if (TOptional<EWindowsRHI> ConfigDefault = Config.DefaultRHI)
{
return ConfigDefault.GetValue();
}
// Find the first RHI with configured support based on the order above
for (EWindowsRHI DefaultRHI : GRHISearchOrder)
{
if (TOptional<ERHIFeatureLevel::Type> HighestFL = ChooseDefaultFeatureLevel(DefaultRHI, Config))
{
return DefaultRHI;
}
}
return EWindowsRHI::D3D11;
}
static TOptional<EWindowsRHI> ChoosePreferredRHI(EWindowsRHI InDefaultRHI)
{
TOptional<EWindowsRHI> RHIPreference{};
// If we are in game, there is a separate setting that can make it prefer D3D12 over D3D11 (but not over other RHIs).
if (!GIsEditor && (InDefaultRHI == EWindowsRHI::D3D11 || InDefaultRHI == EWindowsRHI::D3D12))
{
bool bUseD3D12InGame = false;
bool bPreferFeatureLevelES31 = false;
FString PreferredRHIName;
if (GConfig->GetString(TEXT("D3DRHIPreference"), TEXT("PreferredRHI"), PreferredRHIName, GGameUserSettingsIni))
{
UE_LOG(LogRHI, Log, TEXT("Found D3DRHIPreference PreferredRHI: %s"), *PreferredRHIName);
if (PreferredRHIName == TEXT("dx12"))
{
RHIPreference = EWindowsRHI::D3D12;
}
else if (PreferredRHIName == TEXT("dx11"))
{
RHIPreference = EWindowsRHI::D3D11;
}
else if (PreferredRHIName == TEXT("vulkan"))
{
RHIPreference = EWindowsRHI::Vulkan;
}
else if (PreferredRHIName == TEXT("opengl"))
{
RHIPreference = EWindowsRHI::OpenGL;
}
else
{
UE_LOG(LogRHI, Error, TEXT("unknown RHI name \"%s\" in game user settings, using default"), *PreferredRHIName);
}
}
else if (GConfig->GetBool(TEXT("D3DRHIPreference"), TEXT("bUseD3D12InGame"), bUseD3D12InGame, GGameUserSettingsIni) &&
GConfig->GetBool(TEXT("D3DRHIPreference"), TEXT("bPreferFeatureLevelES31"), bPreferFeatureLevelES31, GGameUserSettingsIni))
{
// Respect legacy GameUserSettings values if present
UE_LOG(LogRHI, Log, TEXT("Found D3DRHIPreference bUseD3D12InGame: %s"), bUseD3D12InGame ? TEXT("true") : TEXT("false"));
UE_LOG(LogRHI, Log, TEXT("Found D3DRHIPreference bPreferFeatureLevelES31: %s"), bPreferFeatureLevelES31 ? TEXT("true") : TEXT("false"));
if (bUseD3D12InGame)
{
RHIPreference = EWindowsRHI::D3D12;
}
else if (bPreferFeatureLevelES31)
{
RHIPreference = EWindowsRHI::D3D11;
}
}
}
return RHIPreference;
}
static TOptional<EWindowsRHI> ChooseForcedRHI(TOptional<ERHIFeatureLevel::Type> ForcedFeatureLevel, const FParsedWindowsDynamicRHIConfig& Config)
{
TOptional<EWindowsRHI> ForcedRHI = {};
// Command line overrides
uint32 Sum = 0;
if (FParse::Param(FCommandLine::Get(), TEXT("vulkan")))
{
ForcedRHI = EWindowsRHI::Vulkan;
Sum++;
}
if (FParse::Param(FCommandLine::Get(), TEXT("opengl")))
{
ForcedRHI = EWindowsRHI::OpenGL;
Sum++;
}
if (FParse::Param(FCommandLine::Get(), TEXT("d3d11")) || FParse::Param(FCommandLine::Get(), TEXT("dx11")))
{
ForcedRHI = EWindowsRHI::D3D11;
Sum++;
}
if (FParse::Param(FCommandLine::Get(), TEXT("d3d12")) || FParse::Param(FCommandLine::Get(), TEXT("dx12")))
{
ForcedRHI = EWindowsRHI::D3D12;
Sum++;
}
if (GetOverrideRHI() != nullptr)
{
ForcedRHI = EWindowsRHI::Imported;
Sum++;
}
#if WITH_EDITOR
if (!ForcedRHI)
{
if (TOptional<ERHIBindlessConfiguration> ForcedConfig = RHIGetForcedBindlessConfiguration(); ForcedConfig && ForcedConfig != ERHIBindlessConfiguration::Disabled)
{
ForcedRHI = EWindowsRHI::D3D12;
}
}
#endif
if (Sum > 1)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RHIOptionsError", "-d3d12/dx12, -d3d11/dx11, -vulkan, -opengl, and -platformrhi are mutually exclusive options, but more than one was specified on the command-line."));
UE_LOG(LogRHI, Fatal, TEXT("-d3d12, -d3d11, -vulkan, -opengl, and -platformrhi are mutually exclusive options, but more than one was specified on the command-line."));
}
// FeatureLevelES31 is also a command line override, so it will determine the underlying RHI unless one is specified
if (FPlatformProperties::RequiresCookedData() && ForcedFeatureLevel)
{
if (ForcedRHI)
{
if (!Config.IsFeatureLevelTargeted(ForcedRHI.GetValue(), ForcedFeatureLevel.GetValue()))
{
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("RHINotSupported", "{0} does not support {1}."), GetTextName(ForcedRHI.GetValue()), GetTextName(ForcedFeatureLevel.GetValue())));
UE_LOG(LogRHI, Fatal, TEXT("%s does not support %s."), GetLogName(ForcedRHI.GetValue()), GetLogName(ForcedFeatureLevel.GetValue()));
}
}
else if ((ForcedRHI = Config.GetFirstRHIWithFeatureLevelSupport(ForcedFeatureLevel.GetValue())))
{
UE_LOG(LogRHI, Log, TEXT("Forcing RHI to %s since Feature Level %s was forced"), GetLogName(ForcedRHI.GetValue()), GetLogName(ForcedFeatureLevel.GetValue()));
}
}
return ForcedRHI;
}
static TOptional<ERHIFeatureLevel::Type> GetForcedFeatureLevel()
{
TOptional<ERHIFeatureLevel::Type> ForcedFeatureLevel{};
if (GetOverrideRHI() != nullptr)
{
ForcedFeatureLevel = GetOverrideRHI()->FeatureLevel;
}
if (FParse::Param(FCommandLine::Get(), TEXT("es31")) || FParse::Param(FCommandLine::Get(), TEXT("FeatureLevelES31")) || FParse::Param(FCommandLine::Get(), TEXT("FeatureLevelES3_1")))
{
ForcedFeatureLevel = ERHIFeatureLevel::ES3_1;
}
if (FParse::Param(FCommandLine::Get(), TEXT("sm5")))
{
ForcedFeatureLevel = ERHIFeatureLevel::SM5;
}
if (FParse::Param(FCommandLine::Get(), TEXT("sm6")))
{
ForcedFeatureLevel = ERHIFeatureLevel::SM6;
}
#if WITH_EDITOR
if (TOptional<ERHIBindlessConfiguration> ForcedConfig = RHIGetForcedBindlessConfiguration(); ForcedConfig && ForcedConfig != ERHIBindlessConfiguration::Disabled)
{
ForcedFeatureLevel = ERHIFeatureLevel::SM6;
}
#endif
return ForcedFeatureLevel;
}
static ERHIFeatureLevel::Type ChooseFeatureLevel(EWindowsRHI ChosenRHI, const TOptional<ERHIFeatureLevel::Type> ForcedFeatureLevel, const FParsedWindowsDynamicRHIConfig& Config, bool& bOutForceD3D11)
{
bOutForceD3D11 = false;
if (ForcedFeatureLevel)
{
// Allow the forced feature level if we're in a position to compile its shaders
if (!FPlatformProperties::RequiresCookedData())
{
UE_LOG(LogRHI, Log, TEXT("Using Forced Feature Level in Editor: %s"), GetLogName(ForcedFeatureLevel.GetValue()));
return ForcedFeatureLevel.GetValue();
}
// Make sure the feature level is supported by the runtime, otherwise fall back to the default
if (Config.IsFeatureLevelTargeted(ChosenRHI, ForcedFeatureLevel.GetValue()))
{
UE_LOG(LogRHI, Log, TEXT("Using Forced Feature Level the Game is configured for: %s"), GetLogName(ForcedFeatureLevel.GetValue()));
return ForcedFeatureLevel.GetValue();
}
}
TOptional<ERHIFeatureLevel::Type> FeatureLevel = GetPreferredFeatureLevel(ChosenRHI, Config, bOutForceD3D11);
if (!FeatureLevel || (FPlatformProperties::RequiresCookedData() && !Config.IsFeatureLevelTargeted(ChosenRHI, FeatureLevel.GetValue())))
{
FeatureLevel = Config.GetHighestSupportedFeatureLevel(ChosenRHI);
// If we were forced to a specific RHI while not forced to a specific feature level and the project isn't configured for it, find the default Feature Level for that RHI
if (!FeatureLevel)
{
FeatureLevel = GetDefaultFeatureLevelForRHI(ChosenRHI);
if (FPlatformProperties::RequiresCookedData())
{
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(LOCTEXT("MissingTargetedShaderFormats", "Unable to launch with RHI '{0}' since the project is not configured to support it."), GetTextName(ChosenRHI)));
UE_LOG(LogRHI, Fatal, TEXT("Unable to launch with RHI '%s' since the project is not configured to support it."), GetLogName(ChosenRHI));
}
else
{
UE_LOG(LogRHI, Warning, TEXT("User requested RHI '%s' but that is not supported by this project's data. Defaulting to Feature Level '%s'."), GetLogName(ChosenRHI), GetLogName(FeatureLevel.GetValue()));
}
}
else
{
UE_LOG(LogRHI, Log, TEXT("Using Highest Feature Level of %s: %s"), GetLogName(ChosenRHI), GetLogName(FeatureLevel.GetValue()));
}
}
// If the user wanted to force a feature level and we couldn't set it, log out why and what we're actually running with
if (ForcedFeatureLevel)
{
UE_LOG(LogRHI, Warning, TEXT("User requested Feature Level '%s' but that is not supported by this project. Falling back to Feature Level '%s'."), GetLogName(ForcedFeatureLevel.GetValue()), GetLogName(FeatureLevel.GetValue()));
}
return FeatureLevel.GetValue();
}
static bool HandleUnsupportedFeatureLevel(EWindowsRHI& WindowsRHI, ERHIFeatureLevel::Type& FeatureLevel, TOptional<ERHIFeatureLevel::Type> ForcedFeatureLevel, const FParsedWindowsDynamicRHIConfig& Config)
{
if (ForcedFeatureLevel)
{
if (WindowsRHI == EWindowsRHI::D3D12 && FeatureLevel == ERHIFeatureLevel::SM6)
{
UE_LOG(LogRHI, Log, TEXT("RHI %s with Feature Level %s is not supported on your system."), GetLogName(WindowsRHI), GetLogName(FeatureLevel));
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RequiredDX12SM6", "DirectX 12 with Feature Level SM6 is not supported on your system. Try running without the -sm6 command line argument."));
FPlatformMisc::RequestExit(true, TEXT("HandleUnsupportedFeatureLevel"));
}
return false;
}
if (FeatureLevel == ERHIFeatureLevel::SM6)
{
GDynamicRHIFailedToInitializeAdvancedPlatform = true;
}
if (TOptional<ERHIFeatureLevel::Type> FallbackFeatureLevel = Config.GetNextHighestTargetedFeatureLevel(WindowsRHI, FeatureLevel))
{
FeatureLevel = FallbackFeatureLevel.GetValue();
return true;
}
return false;
}
static bool HandleUnsupportedRHI(EWindowsRHI& WindowsRHI, ERHIFeatureLevel::Type& FeatureLevel, TOptional<EWindowsRHI> ForcedRHI, const FParsedWindowsDynamicRHIConfig& Config)
{
if (ForcedRHI)
{
if (ForcedRHI == EWindowsRHI::D3D12)
{
UE_LOG(LogRHI, Log, TEXT("RHI %s with Feature Level %s is not supported on your system."), GetLogName(WindowsRHI), GetLogName(FeatureLevel));
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RequiredDX12", "DirectX 12 is not supported on your system. Try running without the -dx12 or -d3d12 command line argument."));
FPlatformMisc::RequestExit(true, TEXT("HandleUnsupportedRHI.ForcedRHI"));
return false;
}
}
if (WindowsRHI == EWindowsRHI::D3D12)
{
if (TOptional<ERHIFeatureLevel::Type> D3D11FeatureLevel = Config.GetHighestSupportedFeatureLevel(EWindowsRHI::D3D11))
{
WindowsRHI = EWindowsRHI::D3D11;
FeatureLevel = D3D11FeatureLevel.GetValue();
return true;
}
}
UE_LOG(LogRHI, Log, TEXT("RHI %s is not supported on your system."), GetLogName(WindowsRHI));
if (WindowsRHI == EWindowsRHI::D3D12)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RequiredDX12", "DirectX 12 is not supported on your system. Try running without the -dx12 or -d3d12 command line argument."));
FPlatformMisc::RequestExit(true, TEXT("HandleUnsupportedRHI.D3D12"));
}
if (WindowsRHI == EWindowsRHI::D3D11)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RequiredDX11Feature_11_SM5", "A D3D11-compatible GPU (Feature Level 11.0, Shader Model 5.0) is required to run the engine."));
FPlatformMisc::RequestExit(true, TEXT("HandleUnsupportedRHI.D3D11"));
}
if (WindowsRHI == EWindowsRHI::Vulkan)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RequiredVulkan", "A compatible Vulkan Driver is required to run the engine."));
FPlatformMisc::RequestExit(true, TEXT("HandleUnsupportedRHI.Vulkan"));
}
if (WindowsRHI == EWindowsRHI::OpenGL)
{
FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("RequiredOpenGL", "OpenGL 4.3 is required to run the engine."));
FPlatformMisc::RequestExit(true, TEXT("HandleUnsupportedRHI.OpenGL"));
}
return false;
}
static IDynamicRHIModule* LoadDynamicRHIModule(ERHIFeatureLevel::Type& DesiredFeatureLevel, const TCHAR*& LoadedRHIModuleName)
{
// Make sure the DDSPI is initialized before we try and read from it
FGenericDataDrivenShaderPlatformInfo::Initialize();
bool bUseGPUCrashDebugging = false;
if (!GIsEditor && GConfig->GetBool(TEXT("D3DRHIPreference"), TEXT("bUseGPUCrashDebugging"), bUseGPUCrashDebugging, GGameUserSettingsIni))
{
auto GPUCrashDebuggingCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.GPUCrashDebugging"));
*GPUCrashDebuggingCVar = bUseGPUCrashDebugging;
}
const FParsedWindowsDynamicRHIConfig Config = ParseWindowsDynamicRHIConfig();
// RHI is chosen by the project settings (first DefaultGraphicsRHI, then TargetedRHIs are consulted, "Default" maps to D3D12).
// After this, a separate game-only setting (does not affect editor) bPreferD3D12InGame selects between D3D12 or D3D11 (but will not have any effect if Vulkan or OpenGL are chosen).
// Commandline switches apply after this and can force an arbitrary RHIs. If RHI isn't supported, the game will refuse to start.
EWindowsRHI DefaultRHI = ChooseDefaultRHI(Config);
const TOptional<EWindowsRHI> PreferredRHI = ChoosePreferredRHI(DefaultRHI);
const TOptional<ERHIFeatureLevel::Type> ForcedFeatureLevel = GetForcedFeatureLevel();
const TOptional<EWindowsRHI> ForcedRHI = ChooseForcedRHI(ForcedFeatureLevel, Config);
EWindowsRHI ChosenRHI = DefaultRHI;
if (ForcedRHI)
{
ChosenRHI = ForcedRHI.GetValue();
UE_LOG(LogRHI, Log, TEXT("Using Forced RHI: %s"), GetLogName(ChosenRHI));
}
else if (PreferredRHI)
{
ChosenRHI = PreferredRHI.GetValue();
UE_LOG(LogRHI, Log, TEXT("Using Preferred RHI: %s"), GetLogName(ChosenRHI));
}
else
{
UE_LOG(LogRHI, Log, TEXT("Using Default RHI: %s"), GetLogName(ChosenRHI));
}
bool bOutForceD3D11 = false;
DesiredFeatureLevel = ChooseFeatureLevel(ChosenRHI, ForcedFeatureLevel, Config, bOutForceD3D11);
if (bOutForceD3D11)
{
ChosenRHI = EWindowsRHI::D3D11;
UE_LOG(LogRHI, Log, TEXT("Using Device RHI: %s"), GetLogName(ChosenRHI));
}
// Load the dynamic RHI module.
bool bTryWithNewConfig = false;
do
{
const FString RHIName = GetRHINameFromWindowsRHI(ChosenRHI, DesiredFeatureLevel);
FApp::SetGraphicsRHI(RHIName);
const TCHAR* ModuleName = ModuleNameFromWindowsRHI(ChosenRHI);
UE_LOG(LogRHI, Log, TEXT("Loading RHI module %s"), ModuleName);
IDynamicRHIModule* DynamicRHIModule = FModuleManager::LoadModulePtr<IDynamicRHIModule>(ModuleName);
UE_LOG(LogRHI, Log, TEXT("Checking if RHI %s with Feature Level %s is supported by your system."), GetLogName(ChosenRHI), GetLogName(DesiredFeatureLevel));
if (DynamicRHIModule && DynamicRHIModule->IsSupported(DesiredFeatureLevel))
{
UE_LOG(LogRHI, Log, TEXT("RHI %s with Feature Level %s is supported and will be used."), GetLogName(ChosenRHI), GetLogName(DesiredFeatureLevel));
LoadedRHIModuleName = ModuleName;
return DynamicRHIModule;
}
const EWindowsRHI PreviousRHI = ChosenRHI;
const ERHIFeatureLevel::Type PreviousFeatureLevel = DesiredFeatureLevel;
bTryWithNewConfig = HandleUnsupportedFeatureLevel(ChosenRHI, DesiredFeatureLevel, ForcedFeatureLevel, Config);
if (!bTryWithNewConfig)
{
bTryWithNewConfig = HandleUnsupportedRHI(ChosenRHI, DesiredFeatureLevel, ForcedRHI, Config);
}
if (bTryWithNewConfig)
{
UE_LOG(LogRHI, Log, TEXT("RHI %s with Feature Level %s is not supported on your system, attempting to fall back to RHI %s with Feature Level %s"),
GetLogName(PreviousRHI), GetLogName(PreviousFeatureLevel),
GetLogName(ChosenRHI), GetLogName(DesiredFeatureLevel));
}
} while (bTryWithNewConfig);
UE_LOG(LogRHI, Log, TEXT("RHI %s with Feature Level %s is not supported on your system. No RHI was supported, failing initialization."), GetLogName(ChosenRHI), GetLogName(DesiredFeatureLevel));
return nullptr;
}
FDynamicRHI* PlatformCreateDynamicRHI()
{
FDynamicRHI* DynamicRHI = nullptr;
ERHIFeatureLevel::Type RequestedFeatureLevel;
const TCHAR* LoadedRHIModuleName;
IDynamicRHIModule* DynamicRHIModule = LoadDynamicRHIModule(RequestedFeatureLevel, LoadedRHIModuleName);
if (DynamicRHIModule)
{
// Create the dynamic RHI.
DynamicRHI = DynamicRHIModule->CreateRHI(RequestedFeatureLevel);
GLoadedRHIModuleName = LoadedRHIModuleName;
}
return DynamicRHI;
}
const TCHAR* GetSelectedDynamicRHIModuleName(bool bCleanup)
{
check(FApp::CanEverRender());
if (GDynamicRHI)
{
check(!!GLoadedRHIModuleName);
return GMaxRHIFeatureLevel == ERHIFeatureLevel::ES3_1 ? GetES31ModuleName(GDynamicRHI->GetInterfaceType()) : GLoadedRHIModuleName;
}
else
{
ERHIFeatureLevel::Type DesiredFeatureLevel;
const TCHAR* RHIModuleName;
IDynamicRHIModule* DynamicRHIModule = LoadDynamicRHIModule(DesiredFeatureLevel, RHIModuleName);
check(DynamicRHIModule);
check(RHIModuleName);
if (bCleanup)
{
FModuleManager::Get().UnloadModule(RHIModuleName);
}
return DesiredFeatureLevel == ERHIFeatureLevel::ES3_1 ? GetES31ModuleNameByString(RHIModuleName) : RHIModuleName;
}
}
#undef LOCTEXT_NAMESPACE