Files
UnrealEngine/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalRHI.cpp
2025-05-18 13:04:45 +08:00

1826 lines
74 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MetalRHI.cpp: Metal device RHI implementation.
=============================================================================*/
#include "MetalRHI.h"
#include "MetalDynamicRHI.h"
#include "MetalRHIPrivate.h"
#include "MetalCommandBuffer.h"
#include "Misc/MessageDialog.h"
#include "Modules/ModuleManager.h"
#include "RenderUtils.h"
#if PLATFORM_IOS
#include "IOS/IOSAppDelegate.h"
#elif PLATFORM_MAC
#include "Mac/MacApplication.h"
#include "HAL/PlatformApplicationMisc.h"
#include "GenericPlatform/GenericPlatformFile.h"
#endif
#include "HAL/FileManager.h"
#include "MetalProfiler.h"
#include "GenericPlatform/GenericPlatformDriver.h"
#include "MetalShaderResources.h"
#include "MetalLLM.h"
#include "Engine/RendererSettings.h"
#include "MetalTransitionData.h"
#include "EngineGlobals.h"
#include "MetalBindlessDescriptors.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "MetalResourceCollection.h"
DEFINE_LOG_CATEGORY(LogMetal)
bool GIsMetalInitialized = false;
FMetalBufferFormat GMetalBufferFormats[PF_MAX];
static TAutoConsoleVariable<int32> CVarUseIOSRHIThread(
TEXT("r.Metal.IOSRHIThread"),
0,
TEXT("Controls RHIThread usage for IOS:\n")
TEXT("\t0: No RHIThread.\n")
TEXT("\t1: Use RHIThread.\n")
TEXT("Default is 0."),
ECVF_Default | ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<int32> CVarMetalParallel(
TEXT("r.Metal.Parallel"),
0,
TEXT("Controls Parallel Translate support for MacOS/IOS:\n")
TEXT("\t0: No Parallel support.\n")
TEXT("\t1: Parallel enabled.\n")
TEXT("Default is 0."),
ECVF_Default | ECVF_RenderThreadSafe
);
// 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<bool> CVarEnableMetalPSOFileCacheWhenPrecachingActive(
TEXT("r.Metal.EnablePSOFileCacheWhenPrecachingActive"),
false,
TEXT("false: If precaching is available (r.PSOPrecaching=1, then disable the PSO filecache. (default)\n")
TEXT("true: Allow both PSO file cache and precaching."),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<bool> CVarEnableMetalDeferredDeleteLatency(
TEXT("r.Metal.EnableMetalDeferredDeleteLatency"),
false,
TEXT("false: No added latency on deferred delete \n")
TEXT("true: Extra latency on deferred delete"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
extern int32 GMetalResourcePurgeOnDelete;
static void ValidateTargetedRHIFeatureLevelExists(EShaderPlatform Platform)
{
bool bSupportsShaderPlatform = false;
#if PLATFORM_MAC
TArray<FString> TargetedShaderFormats;
GConfig->GetArray(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("TargetedRHIs"), TargetedShaderFormats, GEngineIni);
for (FString Name : TargetedShaderFormats)
{
FName ShaderFormatName(*Name);
if (ShaderFormatToLegacyShaderPlatform(ShaderFormatName) == Platform)
{
bSupportsShaderPlatform = true;
break;
}
}
#else
if (Platform == SP_METAL_ES3_1_IOS || Platform == SP_METAL_ES3_1_TVOS || Platform == SP_METAL_SIM)
{
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportsMetal"), bSupportsShaderPlatform, GEngineIni);
}
else if (Platform == SP_METAL_SM5_IOS || Platform == SP_METAL_SM5_TVOS)
{
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportsMetalMRT"), bSupportsShaderPlatform, GEngineIni);
}
#endif
if (!bSupportsShaderPlatform && !WITH_EDITOR)
{
FFormatNamedArguments Args;
Args.Add(TEXT("ShaderPlatform"), FText::FromString(LegacyShaderPlatformToShaderFormat(Platform).ToString()));
FText LocalizedMsg = FText::Format(NSLOCTEXT("MetalRHI", "ShaderPlatformUnavailable","Shader platform: {ShaderPlatform} was not cooked! Please enable this shader platform in the project's target settings."),Args);
FText Title = NSLOCTEXT("MetalRHI", "ShaderPlatformUnavailableTitle","Shader Platform Unavailable");
FMessageDialog::Open(EAppMsgType::Ok, LocalizedMsg, Title);
FPlatformMisc::RequestExit(true);
METAL_FATAL_ERROR(TEXT("Shader platform: %s was not cooked! Please enable this shader platform in the project's target settings."), *LegacyShaderPlatformToShaderFormat(Platform).ToString());
}
}
#if PLATFORM_MAC && WITH_EDITOR
static void VerifyMetalCompiler()
{
FString OutStdOut;
FString OutStdErr;
// Using xcrun or xcodebuild will fire xcode-select if xcode or command line tools are not installed
// This will also issue a popup dialog which will attempt to install command line tools which we don't want from the Editor
// xcode-select --print-path
// Can print out /Applications/Xcode.app/Contents/Developer OR /Library/Developer/CommandLineTools
// CommandLineTools is no good for us as the Metal compiler isn't included
{
int32 ReturnCode = -1;
bool bFoundXCode = false;
FPlatformProcess::ExecProcess(TEXT("/usr/bin/xcode-select"), TEXT("--print-path"), &ReturnCode, &OutStdOut, &OutStdErr);
if(ReturnCode == 0 && OutStdOut.Len() > 0)
{
OutStdOut.RemoveAt(OutStdOut.Len() - 1);
if (IFileManager::Get().DirectoryExists(*OutStdOut))
{
FString XcodeAppPath = OutStdOut.Left(OutStdOut.Find(TEXT(".app/")) + 4);
NSBundle* XcodeBundle = [NSBundle bundleWithPath:XcodeAppPath.GetNSString()];
if (XcodeBundle)
{
bFoundXCode = true;
}
}
}
if(!bFoundXCode)
{
FMessageDialog::Open(EAppMsgType::Ok,
NSLOCTEXT("MetalRHI", "XCodeMissingInstall", "Unreal Engine requires Xcode to compile shaders for Metal. To continue, install Xcode and open it to accept the license agreement. If you install Xcode to any location other than Applications/Xcode, also run the xcode-select command-line tool to specify its location."),
NSLOCTEXT("MetalRHI", "XCodeMissingInstallTitle", "Xcode Not Found"));
FPlatformMisc::RequestExit(true);
return;
}
}
// xcodebuild -license check
// -license check :returns 0 for accepted, otherwise 1 for command line tools or non zero for license not accepted
// -checkFirstLaunchStatus | -runFirstLaunch : returns status and runs first launch not so useful from within the editor as sudo is required
{
int ReturnCode = -1;
FPlatformProcess::ExecProcess(TEXT("/usr/bin/xcodebuild"), TEXT("-license check"), &ReturnCode, &OutStdOut, &OutStdErr);
if(ReturnCode != 0)
{
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(NSLOCTEXT("MetalRHI", "XCodeLicenseAgreement", "Xcode license agreement error: {0}"), FText::FromString(OutStdErr)));
FPlatformMisc::RequestExit(true);
return;
}
}
// xcrun will return non zero if using command line tools
// This can fail for license agreement as well or wrong command line tools set i.e set to /Library/Developer/CommandLineTools rather than Applications/Xcode.app/Contents/Developer
{
int ReturnCode = -1;
FPlatformProcess::ExecProcess(TEXT("/usr/bin/xcrun"), TEXT("-sdk macosx metal -v"), &ReturnCode, &OutStdOut, &OutStdErr);
if(ReturnCode != 0)
{
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(NSLOCTEXT("MetalRHI", "XCodeMetalCompiler", "Xcode Metal Compiler error: {0}"), FText::FromString(OutStdErr)));
FPlatformMisc::RequestExit(true);
return;
}
}
}
#endif
FMetalDynamicRHI::FMetalDynamicRHI(ERHIFeatureLevel::Type RequestedFeatureLevel)
: Device(FMetalDevice::CreateDevice())
, ImmediateContext(*Device, nullptr)
{
FMetalRHICommandContext* RHICommandContext = static_cast<FMetalRHICommandContext*>(RHIGetDefaultContext());
METAL_GPUPROFILE(FMetalProfiler::CreateProfiler(*RHICommandContext));
RHICommandContext->ResetContext();
check(Singleton == nullptr);
Singleton = this;
MTL_SCOPED_AUTORELEASE_POOL;
// This should be called once at the start
check( IsInGameThread() );
check( !GIsThreadedRendering );
#if PLATFORM_MAC && WITH_EDITOR
VerifyMetalCompiler();
#endif
GRHISupportsMultithreading = true;
GRHISupportsMultithreadedResources = true;
// we cannot render to a volume texture without geometry shader or vertex-shader-layer support, so initialise to false and enable based on platform feature availability
GSupportsVolumeTextureRendering = false;
// Metal always needs a render target to render with fragment shaders!
GRHIRequiresRenderTargetForPixelShaderUAVs = true;
GRHIAdapterName = NSStringToFString(Device->GetDevice()->name());
GRHIVendorId = 1; // non-zero to avoid asserts
bool const bRequestedFeatureLevel = (RequestedFeatureLevel != ERHIFeatureLevel::Num);
bool bSupportsPointLights = false;
// get the device to ask about capabilities?
MTL::Device* MTLDevice = Device->GetDevice();
#if PLATFORM_IOS
bool bSupportAppleA8 = false;
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportAppleA8"), bSupportAppleA8, GEngineIni);
bool bIsA8FeatureSet = false;
#if PLATFORM_TVOS
GRHISupportsDrawIndirect = MTLDevice->supportsFeatureSet(MTL::FeatureSet_tvOS_GPUFamily2_v1);
GRHISupportsPixelShaderUAVs = MTLDevice->supportsFeatureSet(MTL::FeatureSet_tvOS_GPUFamily2_v1);
if (!MTLDevice->supportsFeatureSet(MTL::FeatureSet_tvOS_GPUFamily2_v1))
{
bIsA8FeatureSet = true;
}
#else
if (!MTLDevice->supportsFeatureSet(MTL::FeatureSet_iOS_GPUFamily3_v1))
{
bIsA8FeatureSet = true;
}
GRHISupportsRWTextureBuffers = MTLDevice->supportsFeatureSet(MTL::FeatureSet_iOS_GPUFamily4_v1);
GRHISupportsDrawIndirect = MTLDevice->supportsFeatureSet(MTL::FeatureSet_iOS_GPUFamily3_v1);
GRHISupportsPixelShaderUAVs = MTLDevice->supportsFeatureSet(MTL::FeatureSet_iOS_GPUFamily3_v1);
const MTL::FeatureSet FeatureSets[] = {
MTL::FeatureSet_iOS_GPUFamily1_v1,
MTL::FeatureSet_iOS_GPUFamily2_v1,
MTL::FeatureSet_iOS_GPUFamily3_v1,
MTL::FeatureSet_iOS_GPUFamily4_v1
};
const uint8 FeatureSetVersions[][3] = {
{8, 0, 0},
{8, 3, 0},
{10, 0, 0},
{11, 0, 0}
};
GRHIDeviceId = 0;
for (uint32 i = 0; i < 4; i++)
{
if (FPlatformMisc::IOSVersionCompare(FeatureSetVersions[i][0],FeatureSetVersions[i][1],FeatureSetVersions[i][2]) >= 0 && MTLDevice->supportsFeatureSet(FeatureSets[i]))
{
GRHIDeviceId++;
}
}
GSupportsVolumeTextureRendering = Device->SupportsFeature(EMetalFeaturesLayeredRendering);
bSupportsPointLights = GSupportsVolumeTextureRendering;
#endif
if(bIsA8FeatureSet)
{
if(!bSupportAppleA8)
{
UE_LOG(LogMetal, Fatal, TEXT("This device does not supports the Apple A8x or above feature set which is the minimum for this build. Please check the Support Apple A8 checkbox in the IOS Project Settings."));
}
static auto* CVarMobileVirtualTextures = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.VirtualTextures"));
if(CVarMobileVirtualTextures->GetValueOnAnyThread() != 0)
{
UE_LOG(LogMetal, Warning, TEXT("Mobile Virtual Textures require a minimum of the Apple A9 feature set."));
}
}
bool bProjectSupportsMRTs = false;
GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportsMetalMRT"), bProjectSupportsMRTs, GEngineIni);
bool const bRequestedMetalMRT = ((RequestedFeatureLevel >= ERHIFeatureLevel::SM5) || (!bRequestedFeatureLevel && FParse::Param(FCommandLine::Get(),TEXT("metalmrt"))));
bool const bForceES3_1 = FParse::Param(FCommandLine::Get(),TEXT("es31"));
// Only allow SM5 MRT on A9 or above devices
if (bProjectSupportsMRTs && bRequestedMetalMRT && !bIsA8FeatureSet && !bForceES3_1)
{
#if PLATFORM_TVOS
ValidateTargetedRHIFeatureLevelExists(SP_METAL_SM5_IOS);
GMaxRHIShaderPlatform = SP_METAL_SM5_TVOS;
#else
ValidateTargetedRHIFeatureLevelExists(SP_METAL_SM5_IOS);
GMaxRHIShaderPlatform = SP_METAL_SM5_IOS;
#endif
GMaxRHIFeatureLevel = ERHIFeatureLevel::SM5;
}
else
{
if (bRequestedMetalMRT && !bForceES3_1)
{
UE_LOG(LogMetal, Warning, TEXT("Metal MRT support requires an iOS or tvOS device with an A8 processor or later. Falling back to Metal ES 3.1."));
}
#if PLATFORM_TVOS
ValidateTargetedRHIFeatureLevelExists(SP_METAL_ES3_1_TVOS);
GMaxRHIShaderPlatform = SP_METAL_ES3_1_TVOS;
#else
#if WITH_IOS_SIMULATOR
ValidateTargetedRHIFeatureLevelExists(SP_METAL_SIM);
GMaxRHIShaderPlatform = SP_METAL_SIM;
#else
ValidateTargetedRHIFeatureLevelExists(SP_METAL_ES3_1_IOS);
GMaxRHIShaderPlatform = SP_METAL_ES3_1_IOS;
#endif // WITH_IOS_SIMULATOR
#endif // PLATFORM_TVOS
GMaxRHIFeatureLevel = ERHIFeatureLevel::ES3_1;
}
#if USE_STATIC_SHADER_PLATFORM_ENUMS
GMaxRHIShaderPlatform = UE_IOS_STATIC_SHADER_PLATFORM;
#endif
#if USE_STATIC_FEATURE_LEVEL_ENUMS
GMaxRHIFeatureLevel = UE_IOS_STATIC_FEATURE_LEVEL;
#endif
FPlatformMemoryStats Stats = FPlatformMemory::GetStats();
MemoryStats.DedicatedVideoMemory = 0;
MemoryStats.TotalGraphicsMemory = Stats.AvailablePhysical;
MemoryStats.DedicatedSystemMemory = 0;
MemoryStats.SharedSystemMemory = Stats.AvailablePhysical;
#if PLATFORM_TVOS
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES2_REMOVED] = SP_NumPlatforms;
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1] = SP_METAL_ES3_1_TVOS;
#else
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES2_REMOVED] = SP_NumPlatforms;
#if WITH_IOS_SIMULATOR
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1] = SP_METAL_SIM;
#else
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1] = SP_METAL_ES3_1_IOS;
#endif
#endif
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM4_REMOVED] = SP_NumPlatforms;
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM5] = (GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5) ? GMaxRHIShaderPlatform : SP_NumPlatforms;
#else // PLATFORM_IOS
uint32 DeviceIndex = Device->GetDeviceIndex();
TArray<FMacPlatformMisc::FGPUDescriptor> const& GPUs = FPlatformMisc::GetGPUDescriptors();
check(DeviceIndex < GPUs.Num());
FMacPlatformMisc::FGPUDescriptor const& GPUDesc = GPUs[DeviceIndex];
bool bSupportsD24S8 = false;
bool bSupportsD16 = false;
GRHIAdapterName = NSStringToFString(MTLDevice->name());
// However they don't all support other features depending on the version of the OS.
bool bSupportsTiledReflections = false;
bool bSupportsDistanceFields = false;
bool bSupportsSM6 = false;
bool bSupportsSM5 = true;
bool bIsIntelHaswell = false;
GSupportsTimestampRenderQueries = true;
checkf(!GRHIAdapterName.Contains("Nvidia"), TEXT("NVIDIA GPU's are no longer supported in UE 5.4 and above"));
if(GRHIAdapterName.Contains("ATi") || GRHIAdapterName.Contains("AMD"))
{
bSupportsPointLights = true;
GRHIVendorId = (uint32)EGpuVendorId::Amd;
if(GPUDesc.GPUVendorId == GRHIVendorId)
{
GRHIAdapterName = FString(GPUDesc.GPUName);
}
bSupportsTiledReflections = true;
bSupportsDistanceFields = true;
// On AMD can also use completion handler time stamp if macOS < Catalina
GSupportsTimestampRenderQueries = true;
// Only tested on Vega.
GRHISupportsWaveOperations = GRHIAdapterName.Contains(TEXT("Vega"));
if (GRHISupportsWaveOperations)
{
GRHIMinimumWaveSize = 32;
GRHIMaximumWaveSize = 64;
}
}
else if(GRHIAdapterName.Contains("Intel"))
{
bSupportsTiledReflections = false;
bSupportsPointLights = true;
GRHIVendorId = (uint32)EGpuVendorId::Intel;
bSupportsDistanceFields = true;
bIsIntelHaswell = (GRHIAdapterName == TEXT("Intel HD Graphics 5000") || GRHIAdapterName == TEXT("Intel Iris Graphics") || GRHIAdapterName == TEXT("Intel Iris Pro Graphics"));
GRHISupportsWaveOperations = false;
}
else if(GRHIAdapterName.Contains("Apple"))
{
bSupportsPointLights = true;
GRHIVendorId = (uint32)EGpuVendorId::Apple;
bSupportsTiledReflections = true;
bSupportsDistanceFields = true;
GSupportsTimestampRenderQueries = true;
GRHISupportsWaveOperations = true;
GRHIMinimumWaveSize = 32;
GRHIMaximumWaveSize = 32;
// Only MacOS 15.0+ can use SM6 with MSC
if (@available(macOS 15.0, *))
{
bSupportsSM6 = !GRHIAdapterName.Contains("M1");
}
if(bSupportsSM6)
{
// Int64 atomic support was introduced with M2 devices.
GRHISupportsAtomicUInt64 = bSupportsSM6;
GRHIPersistentThreadGroupCount = 1024;
// Disable persistent threads on Apple Silicon (as it doesn't support forward progress guarantee).
IConsoleVariable* NanitePersistentThreadCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Nanite.PersistentThreadsCulling"));
if (NanitePersistentThreadCVar != nullptr && NanitePersistentThreadCVar->GetInt() == 1)
{
NanitePersistentThreadCVar->Set(0);
}
}
}
bool const bRequestedSM6 = RequestedFeatureLevel == ERHIFeatureLevel::SM6 ||
(!bRequestedFeatureLevel && (FParse::Param(FCommandLine::Get(),TEXT("metalsm6"))));
bool const bRequestedSM5 = RequestedFeatureLevel == ERHIFeatureLevel::SM5 ||
(!bRequestedFeatureLevel && (FParse::Param(FCommandLine::Get(),TEXT("metalsm5")) || FParse::Param(FCommandLine::Get(),TEXT("metalmrt"))));
if(bRequestedSM6 && !bSupportsSM6)
{
if(GRHIAdapterName.Contains("Apple") && !GRHIAdapterName.Contains("M1"))
{
UE_LOG(LogMetal, Warning, TEXT("To use SM6 on this system, please ensure you are running Mac OS 15. Falling back to SM5"));
}
else
{
UE_LOG(LogMetal, Warning, TEXT("SM6 is enabled but is not supported on this system, falling back to SM5"));
}
}
if(bSupportsSM6 && bRequestedSM6)
{
GMaxRHIFeatureLevel = ERHIFeatureLevel::SM6;
GMaxRHIShaderPlatform = SP_METAL_SM6;
GRHIGlobals.SupportsNative16BitOps = true;
}
else if(bSupportsSM5 && bRequestedSM5)
{
GMaxRHIFeatureLevel = ERHIFeatureLevel::SM5;
GMaxRHIShaderPlatform = SP_METAL_SM5;
}
else
{
GMaxRHIFeatureLevel = ERHIFeatureLevel::SM5;
GMaxRHIShaderPlatform = SP_METAL_SM5;
}
GRHIGlobals.bSupportsBindless = FDataDrivenShaderPlatformInfo::GetSupportsBindless(GMaxRHIShaderPlatform);
#if PLATFORM_SUPPORTS_MESH_SHADERS
GRHISupportsMeshShadersTier0 = RHISupportsMeshShadersTier0(GMaxRHIShaderPlatform);
GRHISupportsMeshShadersTier1 = RHISupportsMeshShadersTier1(GMaxRHIShaderPlatform);
#endif
ERHIFeatureLevel::Type PreviewFeatureLevel;
if (RHIGetPreviewFeatureLevel(PreviewFeatureLevel))
{
check(PreviewFeatureLevel == ERHIFeatureLevel::ES3_1);
// ES3.1 feature level emulation
GMaxRHIFeatureLevel = PreviewFeatureLevel;
if (GMaxRHIFeatureLevel == ERHIFeatureLevel::ES3_1)
{
GMaxRHIShaderPlatform = SP_METAL_ES3_1;
}
}
// Bindless is technically unlimited so we set 32 as Max UAV's, < SM5 8
GRHIGlobals.MaxSimultaneousUAVs = GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM6 ? 32 : 8;
ValidateTargetedRHIFeatureLevelExists(GMaxRHIShaderPlatform);
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES2_REMOVED] = SP_NumPlatforms;
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::ES3_1] = (GMaxRHIFeatureLevel >= ERHIFeatureLevel::ES3_1) ? SP_METAL_ES3_1 : SP_NumPlatforms;
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM4_REMOVED] = SP_NumPlatforms;
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM5] = (GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5) ? GMaxRHIShaderPlatform : SP_NumPlatforms;
GShaderPlatformForFeatureLevel[ERHIFeatureLevel::SM6] = (GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM6) ? GMaxRHIShaderPlatform : SP_NumPlatforms;
// Mac GPUs support layer indexing.
GSupportsVolumeTextureRendering = true;
bSupportsPointLights &= true;
// Make sure the vendors match - the assumption that order in IORegistry is the order in Metal may not hold up forever.
if(GPUDesc.GPUVendorId == GRHIVendorId)
{
GRHIDeviceId = GPUDesc.GPUDeviceId;
MemoryStats.DedicatedVideoMemory = (int64)GPUDesc.GPUMemoryMB * 1024 * 1024;
MemoryStats.TotalGraphicsMemory = (int64)GPUDesc.GPUMemoryMB * 1024 * 1024;
MemoryStats.DedicatedSystemMemory = 0;
MemoryStats.SharedSystemMemory = 0;
}
// Change the support depth format if we can
bSupportsD24S8 = MTLDevice->depth24Stencil8PixelFormatSupported();
// Disable tiled reflections on Mac Metal for some GPU drivers that ignore the lod-level and so render incorrectly.
if (!bSupportsTiledReflections && !FParse::Param(FCommandLine::Get(),TEXT("metaltiledreflections")))
{
static auto CVarDoTiledReflections = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DoTiledReflections"));
if(CVarDoTiledReflections && CVarDoTiledReflections->GetInt() != 0)
{
CVarDoTiledReflections->Set(0);
}
}
// Disable the distance field AO & shadowing effects on GPU drivers that don't currently execute the shaders correctly.
if ((GMaxRHIShaderPlatform == SP_METAL_SM5 || GMaxRHIShaderPlatform == SP_METAL_SM6) && !bSupportsDistanceFields && !FParse::Param(FCommandLine::Get(),TEXT("metaldistancefields")))
{
static auto CVarDistanceFieldAO = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DistanceFieldAO"));
if(CVarDistanceFieldAO && CVarDistanceFieldAO->GetInt() != 0)
{
CVarDistanceFieldAO->Set(0);
}
static auto CVarDistanceFieldShadowing = IConsoleManager::Get().FindConsoleVariable(TEXT("r.DistanceFieldShadowing"));
if(CVarDistanceFieldShadowing && CVarDistanceFieldShadowing->GetInt() != 0)
{
CVarDistanceFieldShadowing->Set(0);
}
}
#endif
GRHISupportsDynamicResolution = true;
GRHISupportsFrameCyclesBubblesRemoval = true;
GPoolSizeVRAMPercentage = 0;
GTexturePoolSize = 0;
GConfig->GetInt(TEXT("TextureStreaming"), TEXT("PoolSizeVRAMPercentage"), GPoolSizeVRAMPercentage, GEngineIni);
if ( GPoolSizeVRAMPercentage > 0 && MemoryStats.TotalGraphicsMemory > 0 )
{
float PoolSize = float(GPoolSizeVRAMPercentage) * 0.01f * float(MemoryStats.TotalGraphicsMemory);
// 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,
MemoryStats.TotalGraphicsMemory / 1024 / 1024);
}
else
{
static const auto CVarStreamingTexturePoolSize = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Streaming.PoolSize"));
GTexturePoolSize = (int64)CVarStreamingTexturePoolSize->GetValueOnAnyThread() * 1024 * 1024;
UE_LOG(LogRHI,Log,TEXT("Texture pool is %llu MB (of %llu MB total graphics mem)"),
GTexturePoolSize / 1024 / 1024,
MemoryStats.TotalGraphicsMemory / 1024 / 1024);
}
GRHITransitionPrivateData_SizeInBytes = sizeof(FMetalTransitionData);
GRHITransitionPrivateData_AlignInBytes = alignof(FMetalTransitionData);
GRHISupportsRHIThread = false;
if (GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5)
{
GRHISupportsRHIThread = true;
}
else
{
GRHISupportsRHIThread = FParse::Param(FCommandLine::Get(),TEXT("rhithread")) || (CVarUseIOSRHIThread.GetValueOnAnyThread() > 0);
}
bool bSupportsParallel = CVarMetalParallel.GetValueOnAnyThread() || FParse::Param(FCommandLine::Get(),TEXT("rhiparallel"));
GRHISupportsParallelRHIExecute = bSupportsParallel;
GRHIParallelRHIExecuteChildWait = true;
GRHIParallelRHIExecuteParentWait = true;
GRHISupportsParallelRenderPasses = bSupportsParallel;
if (FPlatformMisc::IsDebuggerPresent() && UE_BUILD_DEBUG)
{
#if PLATFORM_IOS // @todo zebra : needs a RENDER_API or whatever
// Enable debug markers if we're running in Xcode
extern int32 GEmitMeshDrawEvent;
GEmitMeshDrawEvent = 1;
#endif
SetEmitDrawEvents(true);
}
// Force disable vertex-shader-layer point light rendering on GPUs that don't support it properly yet.
if(!bSupportsPointLights && !FParse::Param(FCommandLine::Get(),TEXT("metalpointlights")))
{
// Disable point light cubemap shadows on Mac Metal as currently they aren't supported.
static auto CVarCubemapShadows = IConsoleManager::Get().FindConsoleVariable(TEXT("r.AllowPointLightCubemapShadows"));
if(CVarCubemapShadows && CVarCubemapShadows->GetInt() != 0)
{
CVarCubemapShadows->Set(0);
}
}
if (!GSupportsVolumeTextureRendering && !FParse::Param(FCommandLine::Get(),TEXT("metaltlv")))
{
// Disable point light cubemap shadows on Mac Metal as currently they aren't supported.
static auto CVarTranslucentLightingVolume = IConsoleManager::Get().FindConsoleVariable(TEXT("r.TranslucentLightingVolume"));
if(CVarTranslucentLightingVolume && CVarTranslucentLightingVolume->GetInt() != 0)
{
CVarTranslucentLightingVolume->Set(0);
}
}
#if PLATFORM_MAC
if (bIsIntelHaswell)
{
static auto CVarForceDisableVideoPlayback = IConsoleManager::Get().FindConsoleVariable((TEXT("Fort.ForceDisableVideoPlayback")));
if (CVarForceDisableVideoPlayback && CVarForceDisableVideoPlayback->GetInt() != 1)
{
CVarForceDisableVideoPlayback->Set(1);
}
}
#endif
#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
// we don't want to auto-enable draw events in Test
SetEmitDrawEvents(GetEmitDrawEvents() | ENABLE_METAL_GPUEVENTS);
#endif
GSupportsShaderFramebufferFetch = !PLATFORM_MAC && GMaxRHIShaderPlatform != SP_METAL_SM5_IOS && GMaxRHIShaderPlatform != SP_METAL_SM5_TVOS && MobileAllowFramebufferFetch(GMaxRHIShaderPlatform);
GSupportsShaderMRTFramebufferFetch = GSupportsShaderFramebufferFetch;
GHardwareHiddenSurfaceRemoval = true;
GSupportsRenderTargetFormat_PF_G8 = false;
GRHISupportsTextureStreaming = true;
GSupportsWideMRT = true;
GSupportsSeparateRenderTargetBlendState = (GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5);
GRHISupportsPSOPrecaching = true;
GRHISupportsPipelineFileCache = !GRHISupportsPSOPrecaching || CVarEnableMetalPSOFileCacheWhenPrecachingActive.GetValueOnAnyThread();
GRHIGlobals.MaxViewSizeBytesForNonTypedBuffer = MTLDevice->maxBufferLength();
GRHIGlobals.MaxViewDimensionForTypedBuffer = 1 << 28;
#if PLATFORM_MAC
check(MTLDevice->supportsFamily(MTL::GPUFamilyMac2));
GRHISupportsBaseVertexIndex = true;
GRHISupportsFirstInstance = true; // Supported on macOS & iOS but not tvOS.
GMaxTextureDimensions = 16384;
GMaxCubeTextureDimensions = 16384;
GMaxTextureArrayLayers = 2048;
GMaxShadowDepthBufferSizeX = GMaxTextureDimensions;
GMaxShadowDepthBufferSizeY = GMaxTextureDimensions;
bSupportsD16 = true;
GRHISupportsHDROutput = true;
GRHIHDRDisplayOutputFormat = (GRHISupportsHDROutput) ? PF_PLATFORM_HDR_0 : PF_B8G8R8A8;
// Based on the spec below, the maxTotalThreadsPerThreadgroup is not a fixed number but calculated according to the device current ability, so the available threads could less than the maximum number.
// For safety and keep the consistency for all platform, reduce the maximum number to half of the device based.
// https://developer.apple.com/documentation/metal/mtlcomputepipelinedescriptor/2966560-maxtotalthreadsperthreadgroup?language=objc
GMaxWorkGroupInvocations = 512;
#else
//@todo investigate gpufam4
GMaxComputeSharedMemory = 1 << 14;
#if PLATFORM_TVOS
GRHISupportsBaseVertexIndex = false;
GRHISupportsFirstInstance = false; // Supported on macOS & iOS but not tvOS.
GRHISupportsHDROutput = false;
GRHIHDRDisplayOutputFormat = PF_B8G8R8A8; // must have a default value for non-hdr, just like mac or ios
#elif PLATFORM_VISIONOS
GRHISupportsBaseVertexIndex = true;
GRHISupportsFirstInstance = GRHISupportsBaseVertexIndex;
GRHISupportsHDROutput = true;
GRHIHDRDisplayOutputFormat = (GRHISupportsHDROutput) ? PF_PLATFORM_HDR_0 : PF_B8G8R8A8;
GMaxWorkGroupInvocations = 512;
#else
// Only A9+ can support this, so for now we need to limit this to the desktop-forward renderer only.
GRHISupportsBaseVertexIndex = MTLDevice->supportsFeatureSet(MTL::FeatureSet_iOS_GPUFamily3_v1) && (GMaxRHIFeatureLevel >= ERHIFeatureLevel::SM5);
GRHISupportsFirstInstance = GRHISupportsBaseVertexIndex;
// TODO: Move this into IOSPlatform
{
MTL_SCOPED_AUTORELEASE_POOL;
UIScreen* mainScreen = [UIScreen mainScreen];
UIDisplayGamut gamut = mainScreen.traitCollection.displayGamut;
GRHISupportsHDROutput = FPlatformMisc::IOSVersionCompare(10, 0, 0) && gamut == UIDisplayGamutP3;
}
GRHIHDRDisplayOutputFormat = (GRHISupportsHDROutput) ? PF_PLATFORM_HDR_0 : PF_B8G8R8A8;
// Based on the spec below, the maxTotalThreadsPerThreadgroup is not a fixed number but calculated according to the device current ability, so the available threads could less than the maximum number.
// For safety and keep the consistency for all platform, reduce the maximum number to half of the device based.
// https://developer.apple.com/documentation/metal/mtlcomputepipelinedescriptor/2966560-maxtotalthreadsperthreadgroup?language=objc
GMaxWorkGroupInvocations = MTLDevice->supportsFeatureSet(MTL::FeatureSet_iOS_GPUFamily4_v1) ? 512 : 256;
#endif
GMaxTextureDimensions = 8192;
GMaxCubeTextureDimensions = 8192;
GMaxTextureArrayLayers = 2048;
GMaxShadowDepthBufferSizeX = GMaxTextureDimensions;
GMaxShadowDepthBufferSizeY = GMaxTextureDimensions;
#endif
if(MTLDevice->supportsFamily(MTL::GPUFamilyApple6) ||
MTLDevice->supportsFamily(MTL::GPUFamilyMac2))
{
GRHISupportsArrayIndexFromAnyShader = true;
}
GRHIMaxDispatchThreadGroupsPerDimension.X = MAX_uint16;
GRHIMaxDispatchThreadGroupsPerDimension.Y = MAX_uint16;
GRHIMaxDispatchThreadGroupsPerDimension.Z = MAX_uint16;
GMaxTextureMipCount = FPlatformMath::CeilLogTwo( GMaxTextureDimensions ) + 1;
GMaxTextureMipCount = FPlatformMath::Min<int32>( MAX_TEXTURE_MIP_COUNT, GMaxTextureMipCount );
// Initialize the buffer format map - in such a way as to be able to validate it in non-shipping...
#if METAL_DEBUG_OPTIONS
FMemory::Memset(GMetalBufferFormats, 255);
#endif
GMetalBufferFormats[PF_Unknown ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_A32B32G32R32F ] = { MTL::PixelFormatRGBA32Float, (uint8)EMetalBufferFormat::RGBA32Float };
GMetalBufferFormats[PF_B8G8R8A8 ] = { MTL::PixelFormatRGBA8Unorm, (uint8)EMetalBufferFormat::RGBA8Unorm }; // MTL::PixelFormatBGRA8Unorm/EMetalBufferFormat::BGRA8Unorm, < We don't support this as a vertex-format so we have code to swizzle in the shader
GMetalBufferFormats[PF_G8 ] = { MTL::PixelFormatR8Unorm, (uint8)EMetalBufferFormat::R8Unorm };
GMetalBufferFormats[PF_G16 ] = { MTL::PixelFormatR16Unorm, (uint8)EMetalBufferFormat::R16Unorm };
GMetalBufferFormats[PF_DXT1 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_DXT3 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_DXT5 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_UYVY ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_FloatRGB ] = { MTL::PixelFormatRG11B10Float, (uint8)EMetalBufferFormat::RG11B10Half };
GMetalBufferFormats[PF_FloatRGBA ] = { MTL::PixelFormatRGBA16Float, (uint8)EMetalBufferFormat::RGBA16Half };
GMetalBufferFormats[PF_DepthStencil ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ShadowDepth ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R32_FLOAT ] = { MTL::PixelFormatR32Float, (uint8)EMetalBufferFormat::R32Float };
GMetalBufferFormats[PF_G16R16 ] = { MTL::PixelFormatRG16Unorm, (uint8)EMetalBufferFormat::RG16Unorm };
GMetalBufferFormats[PF_G16R16F ] = { MTL::PixelFormatRG16Float, (uint8)EMetalBufferFormat::RG16Half };
GMetalBufferFormats[PF_G16R16F_FILTER ] = { MTL::PixelFormatRG16Float, (uint8)EMetalBufferFormat::RG16Half };
GMetalBufferFormats[PF_G32R32F ] = { MTL::PixelFormatRG32Float, (uint8)EMetalBufferFormat::RG32Float };
GMetalBufferFormats[PF_A2B10G10R10 ] = { MTL::PixelFormatRGB10A2Unorm, (uint8)EMetalBufferFormat::RGB10A2Unorm };
GMetalBufferFormats[PF_A16B16G16R16 ] = { MTL::PixelFormatRGBA16Unorm, (uint8)EMetalBufferFormat::RGBA16Half };
GMetalBufferFormats[PF_D24 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R16F ] = { MTL::PixelFormatR16Float, (uint8)EMetalBufferFormat::RG16Half };
GMetalBufferFormats[PF_R16F_FILTER ] = { MTL::PixelFormatR16Float, (uint8)EMetalBufferFormat::RG16Half };
GMetalBufferFormats[PF_BC5 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_V8U8 ] = { MTL::PixelFormatRG8Snorm, (uint8)EMetalBufferFormat::RG8Unorm };
GMetalBufferFormats[PF_A1 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_FloatR11G11B10 ] = { MTL::PixelFormatRG11B10Float, (uint8)EMetalBufferFormat::RG11B10Half }; // < May not work on tvOS
GMetalBufferFormats[PF_A8 ] = { MTL::PixelFormatA8Unorm, (uint8)EMetalBufferFormat::R8Unorm };
GMetalBufferFormats[PF_R32_UINT ] = { MTL::PixelFormatR32Uint, (uint8)EMetalBufferFormat::R32Uint };
GMetalBufferFormats[PF_R32_SINT ] = { MTL::PixelFormatR32Sint, (uint8)EMetalBufferFormat::R32Sint };
GMetalBufferFormats[PF_PVRTC2 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_PVRTC4 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R16_UINT ] = { MTL::PixelFormatR16Uint, (uint8)EMetalBufferFormat::R16Uint };
GMetalBufferFormats[PF_R16_SINT ] = { MTL::PixelFormatR16Sint, (uint8)EMetalBufferFormat::R16Sint };
GMetalBufferFormats[PF_R16G16B16A16_UINT ] = { MTL::PixelFormatRGBA16Uint, (uint8)EMetalBufferFormat::RGBA16Uint };
GMetalBufferFormats[PF_R16G16B16A16_SINT ] = { MTL::PixelFormatRGBA16Sint, (uint8)EMetalBufferFormat::RGBA16Sint };
GMetalBufferFormats[PF_R5G6B5_UNORM ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::R5G6B5Unorm };
GMetalBufferFormats[PF_B5G5R5A1_UNORM ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::B5G5R5A1Unorm };
GMetalBufferFormats[PF_R8G8B8A8 ] = { MTL::PixelFormatRGBA8Unorm, (uint8)EMetalBufferFormat::RGBA8Unorm };
GMetalBufferFormats[PF_A8R8G8B8 ] = { MTL::PixelFormatRGBA8Unorm, (uint8)EMetalBufferFormat::RGBA8Unorm }; // MTL::PixelFormatBGRA8Unorm/EMetalBufferFormat::BGRA8Unorm, < We don't support this as a vertex-format so we have code to swizzle in the shader
GMetalBufferFormats[PF_BC4 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R8G8 ] = { MTL::PixelFormatRG8Unorm, (uint8)EMetalBufferFormat::RG8Unorm };
GMetalBufferFormats[PF_ATC_RGB ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ATC_RGBA_E ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ATC_RGBA_I ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_X24_G8 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ETC1 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ETC2_RGB ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ETC2_RGBA ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R32G32B32A32_UINT ] = { MTL::PixelFormatRGBA32Uint, (uint8)EMetalBufferFormat::RGBA32Uint };
GMetalBufferFormats[PF_R16G16_UINT ] = { MTL::PixelFormatRG16Uint, (uint8)EMetalBufferFormat::RG16Uint };
GMetalBufferFormats[PF_R16G16_SINT ] = { MTL::PixelFormatRG16Sint, (uint8)EMetalBufferFormat::RG16Sint };
GMetalBufferFormats[PF_R32G32_UINT ] = { MTL::PixelFormatRG32Uint, (uint8)EMetalBufferFormat::RG32Uint };
GMetalBufferFormats[PF_ASTC_4x4 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_6x6 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_8x8 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_10x10 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_12x12 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_4x4_HDR ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_6x6_HDR ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_8x8_HDR ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_10x10_HDR ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_12x12_HDR ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_BC6H ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_BC7 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R8_UINT ] = { MTL::PixelFormatR8Uint, (uint8)EMetalBufferFormat::R8Uint };
GMetalBufferFormats[PF_R8 ] = { MTL::PixelFormatR8Unorm, (uint8)EMetalBufferFormat::R8Unorm };
GMetalBufferFormats[PF_L8 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::R8Unorm };
GMetalBufferFormats[PF_XGXR8 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R8G8B8A8_UINT ] = { MTL::PixelFormatRGBA8Uint, (uint8)EMetalBufferFormat::RGBA8Uint };
GMetalBufferFormats[PF_R8G8B8A8_SNORM ] = { MTL::PixelFormatRGBA8Snorm, (uint8)EMetalBufferFormat::RGBA8Snorm };
GMetalBufferFormats[PF_R16G16B16A16_UNORM ] = { MTL::PixelFormatRGBA16Unorm, (uint8)EMetalBufferFormat::RGBA16Unorm };
GMetalBufferFormats[PF_R16G16B16A16_SNORM ] = { MTL::PixelFormatRGBA16Snorm, (uint8)EMetalBufferFormat::RGBA16Snorm };
GMetalBufferFormats[PF_PLATFORM_HDR_0 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_PLATFORM_HDR_1 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_PLATFORM_HDR_2 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_NV12 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ETC2_R11_EAC ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ETC2_RG11_EAC ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_G16R16_SNORM ] = { MTL::PixelFormatRG16Snorm, (uint8)EMetalBufferFormat::RG16Snorm };
GMetalBufferFormats[PF_R8G8_UINT ] = { MTL::PixelFormatRG8Uint, (uint8)EMetalBufferFormat::RG8Uint };
GMetalBufferFormats[PF_R32G32B32_UINT ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R32G32B32_SINT ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R32G32B32F ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R8_SINT ] = { MTL::PixelFormatR8Sint, (uint8)EMetalBufferFormat::R8Sint };
GMetalBufferFormats[PF_R64_UINT ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R9G9B9EXP5 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_P010 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_4x4_NORM_RG ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_6x6_NORM_RG ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_8x8_NORM_RG ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_10x10_NORM_RG ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_ASTC_12x12_NORM_RG ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
GMetalBufferFormats[PF_R8G8B8 ] = { MTL::PixelFormatInvalid, (uint8)EMetalBufferFormat::Unknown };
static_assert(PF_MAX == 94, "Please setup GMetalBufferFormats properly for the new pixel format");
// Initialize the platform pixel format map.
GPixelFormats[PF_Unknown ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_A32B32G32R32F ].PlatformFormat = (uint32)MTL::PixelFormatRGBA32Float;
GPixelFormats[PF_B8G8R8A8 ].PlatformFormat = (uint32)MTL::PixelFormatBGRA8Unorm;
GPixelFormats[PF_G8 ].PlatformFormat = (uint32)MTL::PixelFormatR8Unorm;
GPixelFormats[PF_G16 ].PlatformFormat = (uint32)MTL::PixelFormatR16Unorm;
GPixelFormats[PF_R32G32B32A32_UINT ].PlatformFormat = (uint32)MTL::PixelFormatRGBA32Uint;
GPixelFormats[PF_R16G16_UINT ].PlatformFormat = (uint32)MTL::PixelFormatRG16Uint;
GPixelFormats[PF_R16G16_SINT ].PlatformFormat = (uint32)MTL::PixelFormatRG16Sint;
GPixelFormats[PF_R32G32_UINT ].PlatformFormat = (uint32)MTL::PixelFormatRG32Uint;
#if PLATFORM_IOS
GPixelFormats[PF_DXT1 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_DXT1 ].Supported = false;
GPixelFormats[PF_DXT3 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_DXT3 ].Supported = false;
GPixelFormats[PF_DXT5 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_DXT5 ].Supported = false;
GPixelFormats[PF_BC4 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_BC4 ].Supported = false;
GPixelFormats[PF_BC5 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_BC5 ].Supported = false;
GPixelFormats[PF_BC6H ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_BC6H ].Supported = false;
GPixelFormats[PF_BC7 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_BC7 ].Supported = false;
GPixelFormats[PF_PVRTC2 ].PlatformFormat = (uint32)MTL::PixelFormatPVRTC_RGBA_2BPP;
GPixelFormats[PF_PVRTC2 ].Supported = true;
GPixelFormats[PF_PVRTC4 ].PlatformFormat = (uint32)MTL::PixelFormatPVRTC_RGBA_4BPP;
GPixelFormats[PF_PVRTC4 ].Supported = true;
GPixelFormats[PF_PVRTC4 ].PlatformFormat = (uint32)MTL::PixelFormatPVRTC_RGBA_4BPP;
GPixelFormats[PF_PVRTC4 ].Supported = true;
GPixelFormats[PF_ASTC_4x4 ].PlatformFormat = (uint32)MTL::PixelFormatASTC_4x4_LDR;
GPixelFormats[PF_ASTC_4x4 ].Supported = true;
GPixelFormats[PF_ASTC_6x6 ].PlatformFormat = (uint32)MTL::PixelFormatASTC_6x6_LDR;
GPixelFormats[PF_ASTC_6x6 ].Supported = true;
GPixelFormats[PF_ASTC_8x8 ].PlatformFormat = (uint32)MTL::PixelFormatASTC_8x8_LDR;
GPixelFormats[PF_ASTC_8x8 ].Supported = true;
GPixelFormats[PF_ASTC_10x10 ].PlatformFormat = (uint32)MTL::PixelFormatASTC_10x10_LDR;
GPixelFormats[PF_ASTC_10x10 ].Supported = true;
GPixelFormats[PF_ASTC_12x12 ].PlatformFormat = (uint32)MTL::PixelFormatASTC_12x12_LDR;
GPixelFormats[PF_ASTC_12x12 ].Supported = true;
#if !PLATFORM_TVOS
if(MTLDevice->supportsFamily(MTL::GPUFamilyApple6))
{
GPixelFormats[PF_ASTC_4x4_HDR].PlatformFormat = (uint32)MTL::PixelFormatASTC_4x4_HDR;
GPixelFormats[PF_ASTC_4x4_HDR].Supported = true;
GPixelFormats[PF_ASTC_6x6_HDR].PlatformFormat = (uint32)MTL::PixelFormatASTC_6x6_HDR;
GPixelFormats[PF_ASTC_6x6_HDR].Supported = true;
GPixelFormats[PF_ASTC_8x8_HDR].PlatformFormat = (uint32)MTL::PixelFormatASTC_8x8_HDR;
GPixelFormats[PF_ASTC_8x8_HDR].Supported = true;
GPixelFormats[PF_ASTC_10x10_HDR].PlatformFormat = (uint32)MTL::PixelFormatASTC_10x10_HDR;
GPixelFormats[PF_ASTC_10x10_HDR].Supported = true;
GPixelFormats[PF_ASTC_12x12_HDR].PlatformFormat = (uint32)MTL::PixelFormatASTC_12x12_HDR;
GPixelFormats[PF_ASTC_12x12_HDR].Supported = true;
}
#endif
// used with virtual textures
GPixelFormats[PF_ETC2_RGB ].PlatformFormat = (uint32)MTL::PixelFormatETC2_RGB8;
GPixelFormats[PF_ETC2_RGB ].Supported = true;
GPixelFormats[PF_ETC2_RGBA ].PlatformFormat = (uint32)MTL::PixelFormatEAC_RGBA8;
GPixelFormats[PF_ETC2_RGBA ].Supported = true;
GPixelFormats[PF_ETC2_R11_EAC ].PlatformFormat = (uint32)MTL::PixelFormatEAC_R11Unorm;
GPixelFormats[PF_ETC2_R11_EAC ].Supported = true;
GPixelFormats[PF_ETC2_RG11_EAC ].PlatformFormat = (uint32)MTL::PixelFormatEAC_RG11Unorm;
GPixelFormats[PF_ETC2_RG11_EAC ].Supported = true;
// IOS HDR format is BGR10_XR (32bits, 3 components)
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockSizeX = 1;
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockSizeY = 1;
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockSizeZ = 1;
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockBytes = 4;
GPixelFormats[PF_PLATFORM_HDR_0 ].NumComponents = 3;
GPixelFormats[PF_PLATFORM_HDR_0 ].PlatformFormat = (uint32)MTL::PixelFormatBGR10_XR_sRGB;
GPixelFormats[PF_PLATFORM_HDR_0 ].Supported = GRHISupportsHDROutput;
#if PLATFORM_TVOS
if (!MTLDevice->supportsFeatureSet(MTL::FeatureSet_tvOS_GPUFamily2_v1))
#else
if (!MTLDevice->supportsFeatureSet(MTL::FeatureSet_iOS_GPUFamily3_v2))
#endif
{
GPixelFormats[PF_FloatRGB ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Float;
GPixelFormats[PF_FloatRGBA ].BlockBytes = 8;
GPixelFormats[PF_FloatR11G11B10 ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Float;
GPixelFormats[PF_FloatR11G11B10 ].BlockBytes = 8;
GPixelFormats[PF_FloatR11G11B10 ].Supported = true;
}
else
{
GPixelFormats[PF_FloatRGB ].PlatformFormat = (uint32)MTL::PixelFormatRG11B10Float;
GPixelFormats[PF_FloatRGB ].BlockBytes = 4;
GPixelFormats[PF_FloatR11G11B10 ].PlatformFormat = (uint32)MTL::PixelFormatRG11B10Float;
GPixelFormats[PF_FloatR11G11B10 ].BlockBytes = 4;
GPixelFormats[PF_FloatR11G11B10 ].Supported = true;
}
GPixelFormats[PF_DepthStencil ].PlatformFormat = (uint32)MTL::PixelFormatDepth32Float_Stencil8;
GPixelFormats[PF_DepthStencil ].BlockBytes = 4;
GPixelFormats[PF_DepthStencil ].Supported = true;
GPixelFormats[PF_ShadowDepth ].PlatformFormat = (uint32)MTL::PixelFormatDepth16Unorm;
GPixelFormats[PF_ShadowDepth ].BlockBytes = 2;
GPixelFormats[PF_ShadowDepth ].Supported = true;
GPixelFormats[PF_D24 ].PlatformFormat = (uint32)MTL::PixelFormatDepth32Float;
GPixelFormats[PF_D24 ].Supported = true;
GPixelFormats[PF_BC5 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_R5G6B5_UNORM ].PlatformFormat = (uint32)MTL::PixelFormatB5G6R5Unorm;
GPixelFormats[PF_R5G6B5_UNORM ].Supported = true;
GPixelFormats[PF_B5G5R5A1_UNORM ].PlatformFormat = (uint32)MTL::PixelFormatBGR5A1Unorm;
GPixelFormats[PF_B5G5R5A1_UNORM ].Supported = true;
#else
GPixelFormats[PF_DXT1 ].PlatformFormat = (uint32)MTL::PixelFormatBC1_RGBA;
GPixelFormats[PF_DXT3 ].PlatformFormat = (uint32)MTL::PixelFormatBC2_RGBA;
GPixelFormats[PF_DXT5 ].PlatformFormat = (uint32)MTL::PixelFormatBC3_RGBA;
GPixelFormats[PF_FloatRGB ].PlatformFormat = (uint32)MTL::PixelFormatRG11B10Float;
GPixelFormats[PF_FloatRGB ].BlockBytes = 4;
GPixelFormats[PF_FloatR11G11B10 ].PlatformFormat = (uint32)MTL::PixelFormatRG11B10Float;
GPixelFormats[PF_FloatR11G11B10 ].BlockBytes = 4;
GPixelFormats[PF_FloatR11G11B10 ].Supported = true;
// Only one HDR format for OSX.
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockSizeX = 1;
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockSizeY = 1;
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockSizeZ = 1;
GPixelFormats[PF_PLATFORM_HDR_0 ].BlockBytes = 8;
GPixelFormats[PF_PLATFORM_HDR_0 ].NumComponents = 4;
GPixelFormats[PF_PLATFORM_HDR_0 ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Float;
GPixelFormats[PF_PLATFORM_HDR_0 ].Supported = GRHISupportsHDROutput;
// Use Depth28_Stencil8 when it is available for consistency
if(bSupportsD24S8)
{
GPixelFormats[PF_DepthStencil ].PlatformFormat = (uint32)MTL::PixelFormatDepth24Unorm_Stencil8;
GPixelFormats[PF_DepthStencil ].bIs24BitUnormDepthStencil = true;
}
else
{
GPixelFormats[PF_DepthStencil ].PlatformFormat = (uint32)MTL::PixelFormatDepth32Float_Stencil8;
GPixelFormats[PF_DepthStencil ].bIs24BitUnormDepthStencil = false;
}
GPixelFormats[PF_DepthStencil ].BlockBytes = 4;
GPixelFormats[PF_DepthStencil ].Supported = true;
if (bSupportsD16)
{
GPixelFormats[PF_ShadowDepth ].PlatformFormat = (uint32)MTL::PixelFormatDepth16Unorm;
GPixelFormats[PF_ShadowDepth ].BlockBytes = 2;
}
else
{
GPixelFormats[PF_ShadowDepth ].PlatformFormat = (uint32)MTL::PixelFormatDepth32Float;
GPixelFormats[PF_ShadowDepth ].BlockBytes = 4;
}
GPixelFormats[PF_ShadowDepth ].Supported = true;
if(bSupportsD24S8)
{
GPixelFormats[PF_D24 ].PlatformFormat = (uint32)MTL::PixelFormatDepth24Unorm_Stencil8;
}
else
{
GPixelFormats[PF_D24 ].PlatformFormat = (uint32)MTL::PixelFormatDepth32Float;
}
GPixelFormats[PF_D24 ].Supported = true;
GPixelFormats[PF_BC4 ].Supported = true;
GPixelFormats[PF_BC4 ].PlatformFormat = (uint32)MTL::PixelFormatBC4_RUnorm;
GPixelFormats[PF_BC5 ].Supported = true;
GPixelFormats[PF_BC5 ].PlatformFormat = (uint32)MTL::PixelFormatBC5_RGUnorm;
GPixelFormats[PF_BC6H ].Supported = true;
GPixelFormats[PF_BC6H ].PlatformFormat = (uint32)MTL::PixelFormatBC6H_RGBUfloat;
GPixelFormats[PF_BC7 ].Supported = true;
GPixelFormats[PF_BC7 ].PlatformFormat = (uint32)MTL::PixelFormatBC7_RGBAUnorm;
GPixelFormats[PF_R5G6B5_UNORM ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_B5G5R5A1_UNORM ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
#endif
GPixelFormats[PF_UYVY ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_FloatRGBA ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Float;
GPixelFormats[PF_FloatRGBA ].BlockBytes = 8;
GPixelFormats[PF_X24_G8 ].PlatformFormat = (uint32)MTL::PixelFormatStencil8;
GPixelFormats[PF_X24_G8 ].BlockBytes = 1;
GPixelFormats[PF_X24_G8 ].Supported = true;
GPixelFormats[PF_R32_FLOAT ].PlatformFormat = (uint32)MTL::PixelFormatR32Float;
#if PLATFORM_MAC
if(MTLDevice->supportsFeatureSet(MTL::FeatureSet_macOS_GPUFamily2_v1))
{
EnumAddFlags(GPixelFormats[PF_R32_FLOAT].Capabilities, EPixelFormatCapabilities::TextureFilterable);
}
#endif
GPixelFormats[PF_G16R16 ].PlatformFormat = (uint32)MTL::PixelFormatRG16Unorm;
GPixelFormats[PF_G16R16 ].Supported = true;
#if PLATFORM_MAC
if(MTLDevice->supportsFeatureSet(MTL::FeatureSet_macOS_GPUFamily2_v1))
{
EnumAddFlags(GPixelFormats[PF_G16R16].Capabilities, EPixelFormatCapabilities::TextureFilterable);
}
#endif
GPixelFormats[PF_G16R16F ].PlatformFormat = (uint32)MTL::PixelFormatRG16Float;
GPixelFormats[PF_G16R16F_FILTER ].PlatformFormat = (uint32)MTL::PixelFormatRG16Float;
GPixelFormats[PF_G32R32F ].PlatformFormat = (uint32)MTL::PixelFormatRG32Float;
GPixelFormats[PF_A2B10G10R10 ].PlatformFormat = (uint32)MTL::PixelFormatRGB10A2Unorm;
GPixelFormats[PF_A16B16G16R16 ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Unorm;
GPixelFormats[PF_R16F ].PlatformFormat = (uint32)MTL::PixelFormatR16Float;
GPixelFormats[PF_R16F_FILTER ].PlatformFormat = (uint32)MTL::PixelFormatR16Float;
GPixelFormats[PF_V8U8 ].PlatformFormat = (uint32)MTL::PixelFormatRG8Snorm;
GPixelFormats[PF_A1 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
// A8 does not allow writes in Metal. So we will fake it with R8.
// If you change this you must also change the swizzle pattern in Platform.ush
// See Texture2DSample_A8 in Common.ush and A8_SAMPLE_MASK in Platform.ush
GPixelFormats[PF_A8 ].PlatformFormat = (uint32)MTL::PixelFormatR8Unorm;
GPixelFormats[PF_R32_UINT ].PlatformFormat = (uint32)MTL::PixelFormatR32Uint;
GPixelFormats[PF_R32_SINT ].PlatformFormat = (uint32)MTL::PixelFormatR32Sint;
GPixelFormats[PF_R16G16B16A16_UINT ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Uint;
GPixelFormats[PF_R16G16B16A16_SINT ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Sint;
GPixelFormats[PF_R8G8B8A8 ].PlatformFormat = (uint32)MTL::PixelFormatRGBA8Unorm;
GPixelFormats[PF_A8R8G8B8 ].PlatformFormat = (uint32)MTL::PixelFormatRGBA8Unorm;
GPixelFormats[PF_R8G8B8A8_UINT ].PlatformFormat = (uint32)MTL::PixelFormatRGBA8Uint;
GPixelFormats[PF_R8G8B8A8_SNORM ].PlatformFormat = (uint32)MTL::PixelFormatRGBA8Snorm;
GPixelFormats[PF_R8G8 ].PlatformFormat = (uint32)MTL::PixelFormatRG8Unorm;
GPixelFormats[PF_R16_SINT ].PlatformFormat = (uint32)MTL::PixelFormatR16Sint;
GPixelFormats[PF_R16_UINT ].PlatformFormat = (uint32)MTL::PixelFormatR16Uint;
GPixelFormats[PF_R8_UINT ].PlatformFormat = (uint32)MTL::PixelFormatR8Uint;
GPixelFormats[PF_R8 ].PlatformFormat = (uint32)MTL::PixelFormatR8Unorm;
GPixelFormats[PF_R16G16B16A16_UNORM ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Unorm;
GPixelFormats[PF_R16G16B16A16_SNORM ].PlatformFormat = (uint32)MTL::PixelFormatRGBA16Snorm;
GPixelFormats[PF_NV12 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_NV12 ].Supported = false;
GPixelFormats[PF_G16R16_SNORM ].PlatformFormat = (uint32)MTL::PixelFormatRG16Snorm;
GPixelFormats[PF_R8G8_UINT ].PlatformFormat = (uint32)MTL::PixelFormatRG8Uint;
GPixelFormats[PF_R32G32B32_UINT ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_R32G32B32_UINT ].Supported = false;
GPixelFormats[PF_R32G32B32_SINT ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_R32G32B32_SINT ].Supported = false;
GPixelFormats[PF_R32G32B32F ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_R32G32B32F ].Supported = false;
GPixelFormats[PF_R8_SINT ].PlatformFormat = (uint32)MTL::PixelFormatR8Sint;
GPixelFormats[PF_R64_UINT ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_R64_UINT ].Supported = false;
GPixelFormats[PF_R9G9B9EXP5 ].PlatformFormat = (uint32)MTL::PixelFormatInvalid;
GPixelFormats[PF_R9G9B9EXP5 ].Supported = false;
#if METAL_DEBUG_OPTIONS
for (uint32 i = 0; i < PF_MAX; i++)
{
checkf((NS::UInteger)GMetalBufferFormats[i].LinearTextureFormat != NS::UIntegerMax, TEXT("Metal linear texture format for pixel-format %s (%d) is not configured!"), GPixelFormats[i].Name, i);
checkf(GMetalBufferFormats[i].DataFormat != 255, TEXT("Metal data buffer format for pixel-format %s (%d) is not configured!"), GPixelFormats[i].Name, i);
}
#endif
RHIInitDefaultPixelFormatCapabilities();
auto AddTypedUAVSupport = [](EPixelFormat InPixelFormat)
{
EnumAddFlags(GPixelFormats[InPixelFormat].Capabilities, EPixelFormatCapabilities::TypedUAVLoad | EPixelFormatCapabilities::TypedUAVStore);
};
switch (MTLDevice->readWriteTextureSupport())
{
case MTL::ReadWriteTextureTier2:
AddTypedUAVSupport(PF_A32B32G32R32F);
AddTypedUAVSupport(PF_R32G32B32A32_UINT);
AddTypedUAVSupport(PF_FloatRGBA);
AddTypedUAVSupport(PF_R16G16B16A16_UINT);
AddTypedUAVSupport(PF_R16G16B16A16_SINT);
AddTypedUAVSupport(PF_R8G8B8A8);
AddTypedUAVSupport(PF_R8G8B8A8_UINT);
AddTypedUAVSupport(PF_R16F);
AddTypedUAVSupport(PF_R16_UINT);
AddTypedUAVSupport(PF_R16_SINT);
AddTypedUAVSupport(PF_R8);
AddTypedUAVSupport(PF_R8_UINT);
// Fall through
case MTL::ReadWriteTextureTier1:
AddTypedUAVSupport(PF_R32_FLOAT);
AddTypedUAVSupport(PF_R32_UINT);
AddTypedUAVSupport(PF_R32_SINT);
// Fall through
case MTL::ReadWriteTextureTierNone:
break;
};
#if PLATFORM_MAC
if(GPUDesc.GPUVendorId == GRHIVendorId)
{
UE_LOG(LogMetal, Display, TEXT(" Vendor ID: %d"), GPUDesc.GPUVendorId);
UE_LOG(LogMetal, Display, TEXT(" Device ID: %d"), GPUDesc.GPUDeviceId);
UE_LOG(LogMetal, Display, TEXT(" VRAM (MB): %d"), GPUDesc.GPUMemoryMB);
}
else
{
UE_LOG(LogMetal, Warning, TEXT("GPU descriptor (%s) from IORegistry failed to match Metal (%s)"), *FString(GPUDesc.GPUName), *GRHIAdapterName);
}
#endif
#if PLATFORM_MAC
if (!FPlatformProcess::IsSandboxedApplication())
{
// Cleanup local BinaryPSOs folder as it's not used anymore.
const FString BinaryPSOsDir = FPaths::ProjectSavedDir() / TEXT("BinaryPSOs");
IPlatformFile::GetPlatformPhysical().DeleteDirectoryRecursively(*BinaryPSOsDir);
}
#endif
#if METAL_RHI_RAYTRACING
if (ImmediateContext.Context->GetDevice().IsRayTracingSupported())
{
if (!FParse::Param(FCommandLine::Get(), TEXT("noraytracing")))
{
GRHISupportsRayTracing = RHISupportsRayTracing(GMaxRHIShaderPlatform);
GRHISupportsRayTracingShaders = RHISupportsRayTracingShaders(GMaxRHIShaderPlatform);
GRHISupportsRayTracingPSOAdditions = false;
GRHISupportsRayTracingAMDHitToken = false;
GRHISupportsInlineRayTracing = GRHISupportsRayTracing && RHISupportsInlineRayTracing(GMaxRHIShaderPlatform);
}
else
{
GRHISupportsRayTracing = false;
}
GRHISupportsRayTracingDispatchIndirect = true;
GRHIRayTracingAccelerationStructureAlignment = 16;
GRHIRayTracingScratchBufferAlignment = 4;
GRHIRayTracingInstanceDescriptorSize = uint32(sizeof(MTLAccelerationStructureUserIDInstanceDescriptor));
}
#endif
GDynamicRHI = this;
// Start the submission and interrupt handler threads
InitializeSubmissionPipe();
GIsMetalInitialized = true;
ImmediateContext.SetProfiler(nullptr);
#if ENABLE_METAL_GPUPROFILE && RHI_NEW_GPU_PROFILER == 0
FMetalProfiler* Profiler = FMetalProfiler::CreateProfiler(ImmediateContext);
ImmediateContext.SetProfiler(Profiler);
if (Profiler)
{
Profiler->BeginFrame();
}
#endif
#if METAL_USE_METAL_SHADER_CONVERTER
CompilerInstance = IRCompilerCreate();
#endif
#if PLATFORM_SUPPORTS_BINDLESS_RENDERING
if (GRHIGlobals.bSupportsBindless)
{
FMetalBindlessDescriptorManager* BindlessDescriptorManager = Device->GetBindlessDescriptorManager();
BindlessDescriptorManager->Init();
}
#endif
}
FMetalDynamicRHI::~FMetalDynamicRHI()
{
check(IsInGameThread() && IsInRenderingThread());
RHIBlockUntilGPUIdle();
ShutdownSubmissionPipe();
GIsMetalInitialized = false;
GIsRHIInitialized = false;
// Ask all initialized FRenderResources to release their RHI resources.
FRenderResource::ReleaseRHIForAllResources();
#if METAL_USE_METAL_SHADER_CONVERTER
IRCompilerDestroy(CompilerInstance);
#endif
#if ENABLE_METAL_GPUPROFILE && RHI_NEW_GPU_PROFILER == 0
FMetalProfiler::DestroyProfiler();
#endif
}
FDynamicRHI::FRHICalcTextureSizeResult FMetalDynamicRHI::RHICalcTexturePlatformSize(FRHITextureDesc const& Desc, uint32 FirstMipIndex)
{
FDynamicRHI::FRHICalcTextureSizeResult Result;
Result.Size = Desc.CalcMemorySizeEstimate(FirstMipIndex);
Result.Align = 0;
return Result;
}
uint64 FMetalDynamicRHI::RHIGetMinimumAlignmentForBufferBackedSRV(EPixelFormat Format)
{
return Device->GetDevice()->minimumLinearTextureAlignmentForPixelFormat((MTL::PixelFormat)GMetalBufferFormats[Format].LinearTextureFormat);
}
void FMetalDynamicRHI::Init()
{
FRenderResource::InitPreRHIResources();
GIsRHIInitialized = true;
}
void FMetalDynamicRHI::RHIEndFrame_RenderThread(FRHICommandListImmediate& RHICmdList)
{
RHICmdList.EnqueueLambdaMultiPipe(ERHIPipeline::Graphics, FRHICommandListBase::EThreadFence::Enabled, TEXT("Metal EndFrame"),
[this](FMetalContextArray const& Contexts)
{
MTL_SCOPED_AUTORELEASE_POOL;
#if RHI_NEW_GPU_PROFILER == 0
FMetalCommandBufferTimer::ResetFrameBufferTimings();
#if ENABLE_METAL_GPUPROFILE
Contexts[ERHIPipeline::Graphics]->GetProfiler()->EndFrame();
#endif
#endif
#if METAL_RHI_RAYTRACING
UpdateRayTracing();
#endif // METAL_RHI_RAYTRACING
});
FDynamicRHI::RHIEndFrame_RenderThread(RHICmdList);
RHICmdList.EnqueueLambdaMultiPipe(ERHIPipeline::Graphics, FRHICommandListBase::EThreadFence::Enabled, TEXT("Metal BeginFrame"),
[this](FMetalContextArray const& Contexts)
{
MTL_SCOPED_AUTORELEASE_POOL;
#if ENABLE_METAL_GPUPROFILE
#if RHI_NEW_GPU_PROFILER == 0
Contexts[ERHIPipeline::Graphics]->GetProfiler()->BeginFrame();
#endif
#endif
});
}
void FMetalDynamicRHI::RHIEndFrame(const FRHIEndFrameArgs& Args)
{
// increment the internal frame counter
Device->IncrementFrameRHIThread();
Device->GarbageCollect();
#if RHI_NEW_GPU_PROFILER
// Close the previous frame's timing and start a new one
auto Lambda = [this, OldTiming = MoveTemp(CurrentTimingPerQueue)]()
{
TArray<UE::RHI::GPUProfiler::FEventStream, TInlineAllocator<GMetalMaxNumQueues>> Streams;
for (auto const& Timing : OldTiming)
{
Streams.Add(MoveTemp(Timing->EventStream));
}
UE::RHI::GPUProfiler::ProcessEvents(Streams);
};
EnqueueEndOfPipeTask(MoveTemp(Lambda), [&](FMetalPayload& 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 = ERHIPipeline::Graphics;
Payload.EventStream.Emplace<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
);
});
#endif
// Pump the interrupt queue to gather completed events
// (required if we're not using an interrupt thread).
ProcessInterruptQueueUntil(nullptr);
}
#if WITH_RHI_BREADCRUMBS
void FMetalRHICommandContext::RHIBeginBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb)
{
const TCHAR* NameStr = nullptr;
FRHIBreadcrumb::FBuffer Buffer;
auto GetNameStr = [&]()
{
if (!NameStr)
{
NameStr = Breadcrumb->GetTCHAR(Buffer);
}
return NameStr;
};
if (ShouldEmitBreadcrumbs())
{
#if ENABLE_METAL_GPUEVENTS
MTL_SCOPED_AUTORELEASE_POOL;
{
// @todo dev-pr avoid TCHAR -> ANSI conversion
CurrentEncoder.PushDebugGroup(NS::String::string(TCHAR_TO_UTF8(GetNameStr()), NS::UTF8StringEncoding));
}
#endif
}
#if ENABLE_METAL_GPUPROFILE
#if RHI_NEW_GPU_PROFILER
if(!CurrentEncoder.IsParallelEncoding())
{
if(!CurrentEncoder.GetCommandBuffer())
{
StartCommandBuffer();
}
FMetalCommandBuffer* CmdBuffer = CurrentEncoder.GetCommandBuffer();
// Can't process breadcrumbs if we are within a render pass
if(Device.SupportsFeature(EMetalFeaturesStageCounterSampling))
{
FMetalBreadcrumbEvent& Event = FMetalBreadcrumbProfiler::GetInstance()->GetBreadcrumbEvent(Breadcrumb, bWithinRenderPass);
Event.TimestampTOP = &CmdBuffer->EmplaceProfilerEvent<UE::RHI::GPUProfiler::FEvent::FBeginBreadcrumb>(Breadcrumb).GPUTimestampTOP;
*Event.TimestampTOP = 0;
CmdBuffer->BeginBreadcrumb(Breadcrumb);
}
}
#else
if (Profiler && Profiler->IsProfilingGPU())
{
Profiler->PushEvent(GetNameStr(), FColor::White);
}
#endif
#endif
}
void FMetalRHICommandContext::RHIEndBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb)
{
#if ENABLE_METAL_GPUPROFILE
#if RHI_NEW_GPU_PROFILER
if(!CurrentEncoder.IsParallelEncoding())
{
if(!CurrentEncoder.GetCommandBuffer())
{
StartCommandBuffer();
}
FMetalCommandBuffer* CmdBuffer = CurrentEncoder.GetCommandBuffer();
if(Device.SupportsFeature(EMetalFeaturesStageCounterSampling))
{
FMetalBreadcrumbEvent& Event = FMetalBreadcrumbProfiler::GetInstance()->GetBreadcrumbEvent(Breadcrumb, bWithinRenderPass);
Event.TimestampBOP = &CmdBuffer->EmplaceProfilerEvent<UE::RHI::GPUProfiler::FEvent::FEndBreadcrumb>(Breadcrumb).GPUTimestampBOP;
*Event.TimestampBOP = 0;
CmdBuffer->EndBreadcrumb(Breadcrumb);
}
}
#else
if (Profiler && Profiler->IsProfilingGPU())
{
Profiler->PopEvent();
}
#endif
#endif
if (ShouldEmitBreadcrumbs())
{
#if ENABLE_METAL_GPUEVENTS
MTL_SCOPED_AUTORELEASE_POOL;
{
CurrentEncoder.PopDebugGroup();
}
#endif
}
}
#endif // WITH_RHI_BREADCRUMBS
void FMetalDynamicRHI::RHIGetSupportedResolution( uint32 &Width, uint32 &Height )
{
#if PLATFORM_MAC
CGDisplayModeRef DisplayMode = FPlatformApplicationMisc::GetSupportedDisplayMode(kCGDirectMainDisplay, Width, Height);
if (DisplayMode)
{
Width = CGDisplayModeGetWidth(DisplayMode);
Height = CGDisplayModeGetHeight(DisplayMode);
CGDisplayModeRelease(DisplayMode);
}
#else
UE_LOG(LogMetal, Warning, TEXT("RHIGetSupportedResolution unimplemented!"));
#endif
}
bool FMetalDynamicRHI::RHIGetAvailableResolutions(FScreenResolutionArray& Resolutions, bool bIgnoreRefreshRate)
{
#if PLATFORM_MAC
const int32 MinAllowableResolutionX = 0;
const int32 MinAllowableResolutionY = 0;
const int32 MaxAllowableResolutionX = 10480;
const int32 MaxAllowableResolutionY = 10480;
const int32 MinAllowableRefreshRate = 0;
const int32 MaxAllowableRefreshRate = 10480;
CFArrayRef AllModes = CGDisplayCopyAllDisplayModes(kCGDirectMainDisplay, NULL);
if (AllModes)
{
const int32 NumModes = CFArrayGetCount(AllModes);
const int32 Scale = (int32)FMacApplication::GetPrimaryScreenBackingScaleFactor();
for (int32 Index = 0; Index < NumModes; Index++)
{
const CGDisplayModeRef Mode = (const CGDisplayModeRef)CFArrayGetValueAtIndex(AllModes, Index);
const int32 Width = (int32)CGDisplayModeGetWidth(Mode) / Scale;
const int32 Height = (int32)CGDisplayModeGetHeight(Mode) / Scale;
const int32 RefreshRate = (int32)CGDisplayModeGetRefreshRate(Mode);
if (Width >= MinAllowableResolutionX && Width <= MaxAllowableResolutionX && Height >= MinAllowableResolutionY && Height <= MaxAllowableResolutionY)
{
bool bAddIt = true;
if (bIgnoreRefreshRate == false)
{
if (RefreshRate < MinAllowableRefreshRate || RefreshRate > MaxAllowableRefreshRate)
{
continue;
}
}
else
{
// See if it is in the list already
for (int32 CheckIndex = 0; CheckIndex < Resolutions.Num(); CheckIndex++)
{
FScreenResolutionRHI& CheckResolution = Resolutions[CheckIndex];
if ((CheckResolution.Width == Width) &&
(CheckResolution.Height == Height))
{
// Already in the list...
bAddIt = false;
break;
}
// Filter out unusable resolutions on notched Macs
else if ((CheckResolution.Width == Width) &&
(CheckResolution.Height != Height))
{
bAddIt = false;
if (Height < CheckResolution.Height)
{
// Only use the shorter (below notch and padding) version
CheckResolution.Height = Height;
}
break;
}
}
}
if (bAddIt)
{
// Add the mode to the list
const int32 Temp2Index = Resolutions.AddZeroed();
FScreenResolutionRHI& ScreenResolution = Resolutions[Temp2Index];
ScreenResolution.Width = Width;
ScreenResolution.Height = Height;
ScreenResolution.RefreshRate = RefreshRate;
}
}
}
CFRelease(AllModes);
}
return true;
#else
UE_LOG(LogMetal, Warning, TEXT("RHIGetAvailableResolutions unimplemented!"));
return false;
#endif
}
void FMetalDynamicRHI::RHIFlushResources()
{
MTL_SCOPED_AUTORELEASE_POOL;
Device->DrainHeap();
}
void* FMetalDynamicRHI::RHIGetNativeDevice()
{
return (void*)Device->GetDevice();
}
void* FMetalDynamicRHI::RHIGetNativeGraphicsQueue()
{
return ImmediateContext.GetCommandQueue().GetQueue();
}
void* FMetalDynamicRHI::RHIGetNativeComputeQueue()
{
return ImmediateContext.GetCommandQueue().GetQueue();
}
void* FMetalDynamicRHI::RHIGetNativeInstance()
{
return (void*)Device;
}
uint16 FMetalDynamicRHI::RHIGetPlatformTextureMaxSampleCount()
{
TArray<ECompositingSampleCount::Type> SamplesArray{ ECompositingSampleCount::Type::One, ECompositingSampleCount::Type::Two, ECompositingSampleCount::Type::Four, ECompositingSampleCount::Type::Eight };
uint16 PlatformMaxSampleCount = ECompositingSampleCount::Type::One;
for (auto sampleIt = SamplesArray.CreateConstIterator(); sampleIt; ++sampleIt)
{
int sample = *sampleIt;
#if PLATFORM_IOS || PLATFORM_MAC
if (!Device->GetDevice()->supportsTextureSampleCount(sample))
{
break;
}
PlatformMaxSampleCount = sample;
#endif
}
return PlatformMaxSampleCount;
}
void FMetalDynamicRHI::RHIBlockUntilGPUIdle()
{
// Submit a new sync point to each queue
TArray<FMetalPayload*> Payloads;
Payloads.Reserve(int32(EMetalQueueType::Count));
TArray<FMetalSyncPointRef, TInlineAllocator<(uint32)EMetalQueueType::Count>> SyncPoints;
for (uint32 QueueTypeIndex = 0; QueueTypeIndex < (uint32)EMetalQueueType::Count; ++QueueTypeIndex)
{
FMetalSyncPointRef SyncPoint = FMetalSyncPoint::Create(EMetalSyncPointType::GPUAndCPU);
FMetalPayload* Payload = new FMetalPayload(Device->GetCommandQueue((EMetalQueueType)QueueTypeIndex));
Payload->SyncPointsToSignal.Add(SyncPoint);
Payload->bAlwaysSignal = true;
Payloads.Add(Payload);
SyncPoints.Add(SyncPoint);
}
SubmitPayloads(MoveTemp(Payloads));
// Block this thread until the sync points have signaled.
for (FMetalSyncPointRef& SyncPoint : SyncPoints)
{
SyncPoint->Wait();
}
}
IRHICommandContext* FMetalDynamicRHI::RHIGetDefaultContext()
{
return &ImmediateContext;
}
IRHIComputeContext* FMetalDynamicRHI::RHIGetCommandContext(ERHIPipeline Pipeline, FRHIGPUMask GPUMask)
{
check(GRHISupportsParallelRHIExecute);
FMetalRHICommandContext* Context = MetalCommandContextPool.Pop();
if (!Context)
{
Context = new FMetalRHICommandContext(*Device, nullptr);
}
Context->ResetContext();
return static_cast<IRHIComputeContext*>(Context);
}
IRHIComputeContext* FMetalDynamicRHI::RHIGetParallelCommandContext(FRHIParallelRenderPassInfo const& ParallelRenderPass, FRHIGPUMask GPUMask)
{
check(GRHISupportsParallelRHIExecute);
FMetalRHICommandContext* Context = MetalCommandContextPool.Pop();
if (!Context)
{
Context = new FMetalRHICommandContext(*Device, nullptr);
}
Context->ResetContext();
Context->SetupParallelContext(&ParallelRenderPass);
return static_cast<IRHIComputeContext*>(Context);
}
class FMetalPlatformCommandList final : public IRHIPlatformCommandList
{
public:
~FMetalPlatformCommandList() {};
TArray<FMetalCommandBuffer*> CommandBuffers;
};
void FMetalDynamicRHI::RHIProcessDeleteQueue()
{
FScopeLock Lock(&ObjectsToDeleteCS);
TArray<FMetalDeferredDeleteObject> Objects = MoveTemp(ObjectsToDelete);
EnqueueEndOfPipeTask([this, LocalDeleteObjects = MoveTemp(Objects)]() mutable
{
for(FMetalDeferredDeleteObject& Object : LocalDeleteObjects)
{
switch (Object.Storage.GetIndex())
{
case FMetalDeferredDeleteObject::TObjectStorage::IndexOfType<NS::Object*>():
{
Object.Storage.Get<NS::Object*>()->release();
break;
}
case FMetalDeferredDeleteObject::TObjectStorage::IndexOfType<FMetalBufferPtr>():
{
FMetalBufferPtr Buffer = Object.Storage.Get<FMetalBufferPtr>();
Buffer->MarkDeleted();
break;
}
case FMetalDeferredDeleteObject::TObjectStorage::IndexOfType<MTLTexturePtr>():
{
MTLTexturePtr Texture = Object.Storage.Get<MTLTexturePtr>();
if (!Texture->buffer() && !Texture->parentTexture())
{
Device->GetResourceHeap().ReleaseTexture(nullptr, Texture);
}
break;
}
#if PLATFORM_SUPPORTS_BINDLESS_RENDERING
case FMetalDeferredDeleteObject::TObjectStorage::IndexOfType<FRHIDescriptorHandle>():
{
FRHIDescriptorHandle Handle = Object.Storage.Get<FRHIDescriptorHandle>();
FMetalBindlessDescriptorManager* BindlessDescriptorManager = Device->GetBindlessDescriptorManager();
check(BindlessDescriptorManager);
BindlessDescriptorManager->FreeDescriptor(Handle);
break;
}
#endif
case FMetalDeferredDeleteObject::TObjectStorage::IndexOfType<FMetalFence*>():
{
FMetalFence* Fence = Object.Storage.Get<FMetalFence*>();
FMetalFencePool::Get().ReleaseFence(Fence);
break;
}
case FMetalDeferredDeleteObject::TObjectStorage::IndexOfType<TUniqueFunction<void()>*>():
{
TUniqueFunction<void()>* Func = Object.Storage.Get<TUniqueFunction<void()>*>();
(*Func)();
delete Func;
break;
}
default:
{
checkNoEntry();
}
}
}
});
}
void FMetalDynamicRHI::EnqueueEndOfPipeTask(TUniqueFunction<void()> TaskFunc, TUniqueFunction<void(FMetalPayload&)> ModifyPayloadCallback)
{
FGraphEventArray Prereqs;
Prereqs.Reserve(GMetalMaxNumQueues + 1);
if (EopTask)
{
Prereqs.Add(EopTask);
}
TArray<FMetalPayload*> Payloads;
Payloads.Reserve(GMetalMaxNumQueues);
ForEachQueue([&](FMetalCommandQueue& Queue)
{
FMetalPayload* Payload = new FMetalPayload(Queue);
FMetalSyncPointRef SyncPoint = FMetalSyncPoint::Create(EMetalSyncPointType::GPUAndCPU);
Payload->SyncPointsToSignal.Emplace(SyncPoint);
Prereqs.Add(SyncPoint->GetGraphEvent());
if (ModifyPayloadCallback)
ModifyPayloadCallback(*Payload);
Payloads.Add(Payload);
});
SubmitPayloads(MoveTemp(Payloads));
EopTask = FFunctionGraphTask::CreateAndDispatchWhenReady(
MoveTemp(TaskFunc),
QUICK_USE_CYCLE_STAT(FExecuteRHIThreadTask, STATGROUP_TaskGraphTasks),
&Prereqs
);
}
void FMetalDynamicRHI::RHIReplaceResources(FRHICommandListBase& RHICmdList, TArray<FRHIResourceReplaceInfo>&& ReplaceInfos)
{
RHICmdList.EnqueueLambda(TEXT("FMetalDynamicRHI::RHIReplaceResources"),
[ReplaceInfos = MoveTemp(ReplaceInfos)](FRHICommandListBase& InRHICmdList)
{
MTL_SCOPED_AUTORELEASE_POOL;
for (FRHIResourceReplaceInfo const& Info : ReplaceInfos)
{
switch (Info.GetType())
{
default:
checkNoEntry();
break;
case FRHIResourceReplaceInfo::EType::Buffer:
{
FMetalRHIBuffer* Dst = ResourceCast(Info.GetBuffer().Dst);
FMetalRHIBuffer* 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(&FMetalRHICommandContext::Get(InRHICmdList));
}
break;
#if METAL_RHI_RAYTRACING
case FRHIResourceReplaceInfo::EType::RTGeometry:
{
FMetalRayTracingGeometry* Dst = ResourceCast(Info.GetRTGeometry().Dst);
FMetalRayTracingGeometry* Src = ResourceCast(Info.GetRTGeometry().Src);
if (!Src)
{
Dst->ReleaseUnderlyingResource();
}
else
{
Dst->Swap(*Src);
}
}
break;
#endif // METAL_RHI_RAYTRACING
}
}
}
);
RHICmdList.RHIThreadFence(true);
}
#if PLATFORM_SUPPORTS_BINDLESS_RENDERING
FRHIResourceCollectionRef FMetalDynamicRHI::RHICreateResourceCollection(FRHICommandListBase& RHICmdList, TConstArrayView<FRHIResourceCollectionMember> InMembers)
{
return new FMetalResourceCollection(RHICmdList, InMembers);
}
#endif
void FMetalDynamicRHI::ForEachQueue(TFunctionRef<void(FMetalCommandQueue&)> Callback)
{
// TODO - Carl: Multiple Queues
Callback(Device->GetCommandQueue(EMetalQueueType::Direct));
}