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

1392 lines
54 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MetalPipeline.cpp: Metal shader pipeline RHI implementation.
=============================================================================*/
#include "MetalPipeline.h"
#include "MetalRHIPrivate.h"
#include "MetalVertexDeclaration.h"
#include "MetalShaderTypes.h"
#include "MetalGraphicsPipelineState.h"
#include "MetalPipeline.h"
#include "MetalShaderResources.h"
#include "MetalProfiler.h"
#include "MetalCommandQueue.h"
#include "MetalCommandBuffer.h"
#include "RenderUtils.h"
#include "Misc/ScopeRWLock.h"
#include "HAL/PThreadEvent.h"
#include "PSOMetrics.h"
#include <objc/runtime.h>
static int32 GMetalCacheShaderPipelines = 1;
static FAutoConsoleVariableRef CVarMetalCacheShaderPipelines(
TEXT("rhi.Metal.CacheShaderPipelines"),
GMetalCacheShaderPipelines,
TEXT("When enabled (1, default) cache all graphics pipeline state objects created in MetalRHI for the life of the program, this trades memory for performance as creating PSOs is expensive in Metal.\n")
TEXT("When set to 2 it will not cache any graphics pipeline state objects\n")
TEXT("Disable in the project configuration to allow PSOs to be released to save memory at the expense of reduced performance and increased hitching in-game\n. (On by default (1))"), ECVF_ReadOnly);
static int32 GMetalCacheMinSize = 32;
static FAutoConsoleVariableRef CVarMetalCacheMinSize(
TEXT("r.ShaderPipelineCache.MetalCacheMinSizeInMB"),
GMetalCacheMinSize,
TEXT("Sets the minimum size that we expect the metal OS cache to be (in MB). This is used to determine if we need to cache PSOs again (Default: 32).\n"), ECVF_ReadOnly);
static int32 GMetalBinaryCacheDebugOutput = 0;
static FAutoConsoleVariableRef CVarMetalBinaryCacheDebugOutput(
TEXT("rhi.Metal.BinaryCacheDebugOutput"),
GMetalBinaryCacheDebugOutput,
TEXT("Enable to output logging information for PSO Binary cache default(0) \n"), ECVF_ReadOnly);
static uint32 BlendBitOffsets[] = { Offset_BlendState0, Offset_BlendState1, Offset_BlendState2, Offset_BlendState3, Offset_BlendState4, Offset_BlendState5, Offset_BlendState6, Offset_BlendState7 };
static uint32 RTBitOffsets[] = { Offset_RenderTargetFormat0, Offset_RenderTargetFormat1, Offset_RenderTargetFormat2, Offset_RenderTargetFormat3, Offset_RenderTargetFormat4, Offset_RenderTargetFormat5, Offset_RenderTargetFormat6, Offset_RenderTargetFormat7 };
static_assert(Offset_RasterEnd < 64 && Offset_End < 128, "Offset_RasterEnd must be < 64 && Offset_End < 128");
static float RoundUpNearestEven(const float f)
{
const float ret = FMath::CeilToFloat(f);
const float isOdd = (float)(((int)ret) & 1);
return ret + isOdd;
}
struct FMetalGraphicsPipelineKey
{
FMetalRenderPipelineHash RenderPipelineHash;
FMetalHashedVertexDescriptor VertexDescriptorHash;
FSHAHash VertexFunction;
#if PLATFORM_SUPPORTS_MESH_SHADERS
FSHAHash MeshFunction;
FSHAHash AmplificationFunction;
#endif
FSHAHash PixelFunction;
template<typename Type>
inline void SetHashValue(uint32 Offset, uint32 NumBits, Type Value)
{
if (Offset < Offset_RasterEnd)
{
uint64 BitMask = ((((uint64)1ULL) << NumBits) - 1) << Offset;
RenderPipelineHash.RasterBits = (RenderPipelineHash.RasterBits & ~BitMask) | (((uint64)Value << Offset) & BitMask);
}
else
{
Offset -= Offset_RenderTargetFormat0;
uint64 BitMask = ((((uint64)1ULL) << NumBits) - 1) << Offset;
RenderPipelineHash.TargetBits = (RenderPipelineHash.TargetBits & ~BitMask) | (((uint64)Value << Offset) & BitMask);
}
}
bool operator==(FMetalGraphicsPipelineKey const& Other) const
{
return (RenderPipelineHash == Other.RenderPipelineHash
&& VertexDescriptorHash == Other.VertexDescriptorHash
&& VertexFunction == Other.VertexFunction
#if PLATFORM_SUPPORTS_MESH_SHADERS
&& MeshFunction == Other.MeshFunction
&& AmplificationFunction == Other.AmplificationFunction
#endif
&& PixelFunction == Other.PixelFunction);
}
friend uint32 GetTypeHash(FMetalGraphicsPipelineKey const& Key)
{
uint32 H = FCrc::MemCrc32(&Key.RenderPipelineHash, sizeof(Key.RenderPipelineHash), GetTypeHash(Key.VertexDescriptorHash));
H = FCrc::MemCrc32(Key.VertexFunction.Hash, sizeof(Key.VertexFunction.Hash), H);
#if PLATFORM_SUPPORTS_MESH_SHADERS
H = FCrc::MemCrc32(Key.MeshFunction.Hash, sizeof(Key.MeshFunction.Hash), H);
H = FCrc::MemCrc32(Key.AmplificationFunction.Hash, sizeof(Key.AmplificationFunction.Hash), H);
#endif
H = FCrc::MemCrc32(Key.PixelFunction.Hash, sizeof(Key.PixelFunction.Hash), H);
return H;
}
friend void InitMetalGraphicsPipelineKey(FMetalDevice& Device, FMetalGraphicsPipelineKey& Key,
const FGraphicsPipelineStateInitializer& Init)
{
uint32 const NumActiveTargets = Init.ComputeNumValidRenderTargets();
check(NumActiveTargets <= MaxSimultaneousRenderTargets);
FMetalBlendState* BlendState = (FMetalBlendState*)Init.BlendState;
FMemory::Memzero(Key.RenderPipelineHash);
bool bHasActiveTargets = false;
for (uint32 i = 0; i < NumActiveTargets; i++)
{
EPixelFormat TargetFormat = (EPixelFormat)Init.RenderTargetFormats[i];
if (TargetFormat == PF_Unknown)
continue;
MTL::PixelFormat MetalFormat = UEToMetalFormat(Device, TargetFormat, EnumHasAnyFlags(Init.RenderTargetFlags[i], TexCreate_SRGB));
uint8 FormatKey = GetMetalPixelFormatKey(MetalFormat);
Key.SetHashValue(RTBitOffsets[i], NumBits_RenderTargetFormat, FormatKey);
Key.SetHashValue(BlendBitOffsets[i], NumBits_BlendState, BlendState->RenderTargetStates[i].BlendStateKey);
bHasActiveTargets |= true;
}
uint8 DepthFormatKey = 0;
uint8 StencilFormatKey = 0;
switch(Init.DepthStencilTargetFormat)
{
case PF_DepthStencil:
{
MTL::PixelFormat MetalFormat = (MTL::PixelFormat)GPixelFormats[PF_DepthStencil].PlatformFormat;
if (Init.DepthTargetLoadAction != ERenderTargetLoadAction::ENoAction || Init.DepthTargetStoreAction != ERenderTargetStoreAction::ENoAction)
{
DepthFormatKey = GetMetalPixelFormatKey(MetalFormat);
}
if (Init.StencilTargetLoadAction != ERenderTargetLoadAction::ENoAction || Init.StencilTargetStoreAction != ERenderTargetStoreAction::ENoAction)
{
StencilFormatKey = GetMetalPixelFormatKey(MTL::PixelFormatStencil8);
}
bHasActiveTargets |= true;
break;
}
case PF_ShadowDepth:
{
DepthFormatKey = GetMetalPixelFormatKey((MTL::PixelFormat)GPixelFormats[PF_ShadowDepth].PlatformFormat);
bHasActiveTargets |= true;
break;
}
default:
{
break;
}
}
// If the pixel shader writes depth then we must compile with depth access, so we may bind the dummy depth.
// If the pixel shader writes to UAVs but not target is bound we must also bind the dummy depth.
FMetalPixelShader* PixelShader = (FMetalPixelShader*)Init.BoundShaderState.PixelShaderRHI;
if ( PixelShader && ( ( PixelShader->Bindings.InOutMask.IsFieldEnabled(CrossCompiler::FShaderBindingInOutMask::DepthStencilMaskIndex) && DepthFormatKey == 0 ) || (bHasActiveTargets == false && PixelShader->Bindings.NumUAVs > 0) ) )
{
MTL::PixelFormat MetalFormat = (MTL::PixelFormat)GPixelFormats[PF_DepthStencil].PlatformFormat;
DepthFormatKey = GetMetalPixelFormatKey(MetalFormat);
}
Key.SetHashValue(Offset_DepthFormat, NumBits_DepthFormat, DepthFormatKey);
Key.SetHashValue(Offset_StencilFormat, NumBits_StencilFormat, StencilFormatKey);
Key.SetHashValue(Offset_SampleCount, NumBits_SampleCount, Init.NumSamples);
Key.SetHashValue(Offset_AlphaToCoverage, NumBits_AlphaToCoverage, Init.NumSamples > 1 && BlendState->bUseAlphaToCoverage ? 1 : 0);
#if PLATFORM_MAC
Key.SetHashValue(Offset_PrimitiveTopology, NumBits_PrimitiveTopology, TranslatePrimitiveTopology(Init.PrimitiveType));
#endif
#if PLATFORM_SUPPORTS_MESH_SHADERS
FMetalMeshShader* MeshShader = (FMetalMeshShader*)Init.BoundShaderState.GetMeshShader();
FMetalGeometryShader* GeometryShader = (FMetalGeometryShader*)Init.BoundShaderState.GetGeometryShader();
if (MeshShader)
{
Key.MeshFunction = MeshShader->GetHash();
FMetalAmplificationShader* AmplificationShader = (FMetalAmplificationShader*)Init.BoundShaderState.GetAmplificationShader();
if (AmplificationShader)
{
Key.AmplificationFunction = AmplificationShader->GetHash();
}
}
#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
else if (GeometryShader)
{
Key.MeshFunction = GeometryShader->GetHash();
}
#endif
else
#endif
{
FMetalVertexDeclaration* VertexDecl = (FMetalVertexDeclaration*)Init.BoundShaderState.VertexDeclarationRHI;
Key.VertexDescriptorHash = VertexDecl->Layout;
FMetalVertexShader* VertexShader = (FMetalVertexShader*)Init.BoundShaderState.VertexShaderRHI;
Key.VertexFunction = VertexShader->GetHash();
}
if (PixelShader)
{
Key.PixelFunction = PixelShader->GetHash();
}
}
};
static FMetalShaderPipelinePtr CreateMTLRenderPipeline(FMetalDevice& Device, bool const bSync, FMetalGraphicsPipelineKey const& Key, const FGraphicsPipelineStateInitializer& Init, FMetalGraphicsPipelineState const* State);
class FMetalShaderPipelineCache
{
public:
FMetalShaderPipelineCache(FMetalDevice& InDevice) : Device(InDevice)
{}
static void Create(FMetalDevice& Device)
{
Self = new FMetalShaderPipelineCache(Device);
}
static FMetalShaderPipelineCache& Get()
{
check(Self);
return *Self;
}
FMetalShaderPipelinePtr GetRenderPipeline(bool const bSync, FMetalGraphicsPipelineState const* State, const FGraphicsPipelineStateInitializer& Init)
{
SCOPE_CYCLE_COUNTER(STAT_MetalPipelineStateTime);
FMetalGraphicsPipelineKey Key;
InitMetalGraphicsPipelineKey(Device, Key, Init);
FMetalShaderPipelinePtr Desc = nullptr;
// Don't cache anything if the cvar is set to 2
if (GMetalCacheShaderPipelines != 2)
{
// By default there'll be more threads trying to read this than to write it.
PipelineMutex.ReadLock();
// Try to find the entry in the cache.
Desc = Pipelines.FindRef(Key);
PipelineMutex.ReadUnlock();
if (Desc == nullptr)
{
// By default there'll be more threads trying to read this than to write it.
EventsMutex.ReadLock();
// Try to find a pipeline creation event for this key. If it's found, we already have a thread creating this pipeline and we just have to wait.
TSharedPtr<FPThreadEvent, ESPMode::ThreadSafe> Event = PipelineEvents.FindRef(Key);
EventsMutex.ReadUnlock();
bool bCompile = false;
if (!Event.IsValid())
{
// Create an event other threads can use to wait if they request the same pipeline this thread is creating
EventsMutex.WriteLock();
Event = PipelineEvents.FindRef(Key);
if (!Event.IsValid())
{
Event = PipelineEvents.Add(Key, MakeShareable(new FPThreadEvent()));
Event->Create(true);
bCompile = true;
}
check(Event.IsValid());
EventsMutex.WriteUnlock();
}
if (bCompile)
{
const double CompilationStartTime = FPlatformTime::Seconds();
Desc = CreateMTLRenderPipeline(Device, bSync, Key, Init, State);
const float CompilationDuration = static_cast<float>(FPlatformTime::Seconds() - CompilationStartTime);
AccumulatePSOMetrics(CompilationDuration);
if (Desc != nullptr)
{
PipelineMutex.WriteLock();
Pipelines.Add(Key, Desc);
ReverseLookup.Add(Desc, Key);
PipelineMutex.WriteUnlock();
}
EventsMutex.WriteLock();
Event->Trigger();
PipelineEvents.Remove(Key);
EventsMutex.WriteUnlock();
}
else
{
check(Event.IsValid());
Event->Wait();
PipelineMutex.ReadLock();
Desc = Pipelines.FindRef(Key);
PipelineMutex.ReadUnlock();
check(Desc);
}
}
}
else
{
Desc = CreateMTLRenderPipeline(Device, bSync, Key, Init, State);
}
return Desc;
}
void ReleaseRenderPipeline(FMetalShaderPipelinePtr Pipeline)
{
// For render pipeline states we might need to remove the PSO from the cache when we aren't caching them for program lifetime
if (GMetalCacheShaderPipelines == 0)
{
FMetalShaderPipelineCache::Get().RemoveRenderPipeline(Pipeline);
}
}
void RemoveRenderPipeline(FMetalShaderPipelinePtr Pipeline)
{
check (GMetalCacheShaderPipelines == 0);
{
FRWScopeLock Lock(PipelineMutex, SLT_Write);
FMetalGraphicsPipelineKey* Desc = ReverseLookup.Find(Pipeline);
if (Desc)
{
Pipelines.Remove(*Desc);
ReverseLookup.Remove(Pipeline);
}
}
}
void Destroy()
{
FRWScopeLock Lock(PipelineMutex, SLT_Write);
Pipelines.Empty();
ReverseLookup.Empty();
PipelineEvents.Empty();
}
private:
static FMetalShaderPipelineCache* Self;
FMetalDevice& Device;
FRWLock PipelineMutex;
FRWLock EventsMutex;
TMap<FMetalGraphicsPipelineKey, FMetalShaderPipelinePtr> Pipelines;
TMap<FMetalShaderPipelinePtr, FMetalGraphicsPipelineKey> ReverseLookup;
TMap<FMetalGraphicsPipelineKey, TSharedPtr<FPThreadEvent, ESPMode::ThreadSafe>> PipelineEvents;
};
FMetalShaderPipelineCache* FMetalShaderPipelineCache::Self = nullptr;
void ShutdownPipelineCache()
{
FMetalShaderPipelineCache::Get().Destroy();
delete &FMetalShaderPipelineCache::Get();
}
FMetalShaderPipeline::~FMetalShaderPipeline()
{
#if METAL_DEBUG_OPTIONS
if(VertexSource)
{
VertexSource->release();
VertexSource = nullptr;
}
if(FragmentSource)
{
FragmentSource->release();
FragmentSource = nullptr;
}
if(ComputeSource)
{
ComputeSource->release();
ComputeSource = nullptr;
}
#endif
}
void FMetalShaderPipeline::InitResourceMask()
{
if (RenderPipelineReflection)
{
InitResourceMask(EMetalShaderVertex);
InitResourceMask(EMetalShaderFragment);
if (Device.GetRuntimeDebuggingLevel() < EMetalDebugLevelValidation)
{
RenderPipelineReflection.reset();
}
}
if (ComputePipelineReflection)
{
InitResourceMask(EMetalShaderCompute);
if (Device.GetRuntimeDebuggingLevel() < EMetalDebugLevelValidation)
{
ComputePipelineReflection.reset();
}
}
if (StreamPipelineReflection)
{
InitResourceMask(EMetalShaderStream);
if (Device.GetRuntimeDebuggingLevel() < EMetalDebugLevelValidation)
{
StreamPipelineReflection.reset();
}
}
}
void FMetalShaderPipeline::InitResourceMask(EMetalShaderFrequency Frequency)
{
if (@available(macOS 13.0, iOS 16.0, *))
{
NS::Array* Bindings = nullptr;
switch(Frequency)
{
case EMetalShaderVertex:
{
check(RenderPipelineReflection);
Bindings = RenderPipelineReflection->vertexBindings();
break;
}
case EMetalShaderFragment:
{
check(RenderPipelineReflection);
Bindings = RenderPipelineReflection->fragmentBindings();
break;
}
case EMetalShaderCompute:
{
check(ComputePipelineReflection);
Bindings = ComputePipelineReflection->bindings();
break;
}
case EMetalShaderStream:
{
check(StreamPipelineReflection);
Bindings = StreamPipelineReflection->vertexBindings();
break;
}
default:
check(false);
break;
}
for (uint32 i = 0; i < Bindings->count(); i++)
{
MTL::Binding* Binding = (MTL::Binding*)Bindings->object(i);
check(Binding);
if (!Binding->used())
{
continue;
}
switch(Binding->type())
{
case MTL::BindingTypeBuffer:
{
MTL::BufferBinding* BufferBinding = (MTL::BufferBinding*)Binding;
checkf(Binding->index() < ML_MaxBuffers, TEXT("Metal buffer index exceeded!"));
if (NSStringToFString(Binding->name()) != TEXT("BufferSizes") && NSStringToFString(Binding->name()) != TEXT("spvBufferSizeConstants"))
{
ResourceMask[Frequency].BufferMask |= (1 << Binding->index());
if(BufferDataSizes[Frequency].Num() < 31)
BufferDataSizes[Frequency].SetNumZeroed(31);
BufferDataSizes[Frequency][Binding->index()] = BufferBinding->bufferDataSize();
}
break;
}
case MTL::BindingTypeThreadgroupMemory:
{
break;
}
case MTL::BindingTypeTexture:
{
MTL::TextureBinding* TextureBinding = (MTL::TextureBinding*)Bindings->object(i);
checkf(Binding->index() < ML_MaxTextures, TEXT("Metal texture index exceeded!"));
ResourceMask[Frequency].TextureMask |= (FMetalTextureMask(1) << Binding->index());
TextureTypes[Frequency].Add(Binding->index(), (uint8)TextureBinding->textureType());
break;
}
case MTL::BindingTypeSampler:
{
checkf(Binding->index() < ML_MaxSamplers, TEXT("Metal sampler index exceeded!"));
ResourceMask[Frequency].SamplerMask |= (1 << Binding->index());
break;
}
default:
check(false);
break;
}
}
}
else
{
NS::Array* Arguments = nullptr;
switch(Frequency)
{
case EMetalShaderVertex:
{
check(RenderPipelineReflection);
Arguments = RenderPipelineReflection->vertexArguments();
break;
}
case EMetalShaderFragment:
{
check(RenderPipelineReflection);
Arguments = RenderPipelineReflection->fragmentArguments();
break;
}
case EMetalShaderCompute:
{
check(ComputePipelineReflection);
Arguments = ComputePipelineReflection->arguments();
break;
}
case EMetalShaderStream:
{
check(StreamPipelineReflection);
Arguments = StreamPipelineReflection->vertexArguments();
break;
}
default:
check(false);
break;
}
for (uint32 i = 0; i < Arguments->count(); i++)
{
MTL::Argument* Argument = (MTL::Argument*)Arguments->object(i);
check(Argument);
if (!Argument->active())
{
continue;
}
switch(Argument->type())
{
case MTL::ArgumentTypeBuffer:
{
checkf(Argument->index() < ML_MaxBuffers, TEXT("Metal buffer index exceeded!"));
if (NSStringToFString(Argument->name()) != TEXT("BufferSizes") && NSStringToFString(Argument->name()) != TEXT("spvBufferSizeConstants"))
{
ResourceMask[Frequency].BufferMask |= (1 << Argument->index());
if(BufferDataSizes[Frequency].Num() < 31)
BufferDataSizes[Frequency].SetNumZeroed(31);
BufferDataSizes[Frequency][Argument->index()] = Argument->bufferDataSize();
}
break;
}
case MTL::ArgumentTypeThreadgroupMemory:
{
break;
}
case MTL::ArgumentTypeTexture:
{
checkf(Argument->index() < ML_MaxTextures, TEXT("Metal texture index exceeded!"));
ResourceMask[Frequency].TextureMask |= (FMetalTextureMask(1) << Argument->index());
TextureTypes[Frequency].Add(Argument->index(), (uint8)Argument->textureType());
break;
}
case MTL::ArgumentTypeSampler:
{
checkf(Argument->index() < ML_MaxSamplers, TEXT("Metal sampler index exceeded!"));
ResourceMask[Frequency].SamplerMask |= (1 << Argument->index());
break;
}
default:
check(false);
break;
}
}
}
}
static MTLVertexDescriptorPtr GetMaskedVertexDescriptor(MTLVertexDescriptorPtr InputDesc, const CrossCompiler::FShaderBindingInOutMask& InOutMask)
{
for (uint32 Attr = 0; Attr < MaxMetalStreams; Attr++)
{
if (!InOutMask.IsFieldEnabled((int32)Attr) && InputDesc->attributes()->object(Attr) != nullptr)
{
MTLVertexDescriptorPtr Desc = NS::TransferPtr(InputDesc->copy());
CrossCompiler::FShaderBindingInOutMask BuffersUsed;
for (int32 MetalStreamIndex = 0; MetalStreamIndex < MaxMetalStreams; ++MetalStreamIndex)
{
if (!InOutMask.IsFieldEnabled(MetalStreamIndex))
{
Desc->attributes()->setObject(nullptr, MetalStreamIndex);
}
else
{
BuffersUsed.EnableField(Desc->attributes()->object(MetalStreamIndex)->bufferIndex());
}
}
for (int32 BufferIndex = 0; BufferIndex < ML_MaxBuffers; ++BufferIndex)
{
if (!BuffersUsed.IsFieldEnabled(BufferIndex))
{
Desc->layouts()->setObject(nullptr, BufferIndex);
}
}
return Desc;
}
}
return InputDesc;
}
template <class TDescriptorType>
static bool ConfigureRenderPipelineDescriptor(FMetalDevice& Device, TDescriptorType* RenderPipelineDesc,
FMetalGraphicsPipelineKey const& Key,
const FGraphicsPipelineStateInitializer& Init)
{
constexpr bool bIsRenderPipelineDesc = std::is_same_v<TDescriptorType, MTL::RenderPipelineDescriptor>;
FMetalPixelShader* PixelShader = (FMetalPixelShader*)Init.BoundShaderState.PixelShaderRHI;
uint32 const NumActiveTargets = Init.ComputeNumValidRenderTargets();
check(NumActiveTargets <= MaxSimultaneousRenderTargets);
if (PixelShader)
{
const uint32 MaxNumActiveTargets = __builtin_popcount(PixelShader->Bindings.InOutMask.Bitmask & ((1u << CrossCompiler::FShaderBindingInOutMask::MaxIndex) - 1));
UE_CLOG((NumActiveTargets < MaxNumActiveTargets), LogMetal, Verbose, TEXT("NumActiveTargets doesn't match pipeline's pixel shader output mask: %u, %hx"), NumActiveTargets, PixelShader->Bindings.InOutMask.Bitmask);
}
FMetalBlendState* BlendState = (FMetalBlendState*)Init.BlendState;
MTL::RenderPipelineColorAttachmentDescriptorArray* ColorAttachments = RenderPipelineDesc->colorAttachments();
uint32 TargetWidth = 0;
for (uint32 ActiveTargetIndex = 0; ActiveTargetIndex < NumActiveTargets; ActiveTargetIndex++)
{
EPixelFormat TargetFormat = (EPixelFormat)Init.RenderTargetFormats[ActiveTargetIndex];
const bool bIsActiveTargetBound = (PixelShader && PixelShader->Bindings.InOutMask.IsFieldEnabled(ActiveTargetIndex));
METAL_FATAL_ASSERT(!(TargetFormat == PF_Unknown && bIsActiveTargetBound), TEXT("Pipeline pixel shader expects target %u to be bound but it isn't: %s."), ActiveTargetIndex, *NSStringToFString(PixelShader->GetSourceCode()));
TargetWidth += GPixelFormats[TargetFormat].BlockBytes;
MTL::PixelFormat MetalFormat = UEToMetalFormat(Device, TargetFormat, EnumHasAnyFlags(Init.RenderTargetFlags[ActiveTargetIndex], TexCreate_SRGB));
MTL::RenderPipelineColorAttachmentDescriptor* Attachment = ColorAttachments->object(ActiveTargetIndex);
Attachment->setPixelFormat(MetalFormat);
MTL::RenderPipelineColorAttachmentDescriptor* Blend = BlendState->RenderTargetStates[ActiveTargetIndex].BlendState;
if(TargetFormat != PF_Unknown)
{
// assign each property manually, would be nice if this was faster
Attachment->setBlendingEnabled(Blend->blendingEnabled());
Attachment->setSourceRGBBlendFactor(Blend->sourceRGBBlendFactor());
Attachment->setDestinationRGBBlendFactor(Blend->destinationRGBBlendFactor());
Attachment->setRgbBlendOperation(Blend->rgbBlendOperation());
Attachment->setSourceAlphaBlendFactor(Blend->sourceAlphaBlendFactor());
Attachment->setDestinationAlphaBlendFactor(Blend->destinationAlphaBlendFactor());
Attachment->setAlphaBlendOperation(Blend->alphaBlendOperation());
Attachment->setWriteMask(Blend->writeMask());
}
else
{
Attachment->setBlendingEnabled(NO);
Attachment->setWriteMask(MTL::ColorWriteMaskNone);
}
}
// don't allow a PSO that is too wide
if (!GSupportsWideMRT && TargetWidth > 16)
{
return false;
}
switch(Init.DepthStencilTargetFormat)
{
case PF_X24_G8:
case PF_DepthStencil:
{
MTL::PixelFormat MetalFormat = (MTL::PixelFormat)GPixelFormats[Init.DepthStencilTargetFormat].PlatformFormat;
if(MetalFormat == MTL::PixelFormatDepth32Float)
{
if (Init.DepthTargetLoadAction != ERenderTargetLoadAction::ENoAction || Init.DepthTargetStoreAction != ERenderTargetStoreAction::ENoAction)
{
RenderPipelineDesc->setDepthAttachmentPixelFormat(MetalFormat);
}
if (Init.StencilTargetLoadAction != ERenderTargetLoadAction::ENoAction || Init.StencilTargetStoreAction != ERenderTargetStoreAction::ENoAction)
{
RenderPipelineDesc->setStencilAttachmentPixelFormat(MTL::PixelFormatStencil8);
}
}
else
{
RenderPipelineDesc->setDepthAttachmentPixelFormat(MetalFormat);
RenderPipelineDesc->setStencilAttachmentPixelFormat(MetalFormat);
}
break;
}
case PF_ShadowDepth:
case PF_D24:
{
RenderPipelineDesc->setDepthAttachmentPixelFormat((MTL::PixelFormat)GPixelFormats[Init.DepthStencilTargetFormat].PlatformFormat);
break;
}
case PF_Unknown:
{
RenderPipelineDesc->setDepthAttachmentPixelFormat(MTL::PixelFormatInvalid);
RenderPipelineDesc->setStencilAttachmentPixelFormat(MTL::PixelFormatInvalid);
break;
}
default:
{
break;
}
}
if (bIsRenderPipelineDesc)
{
check(Init.BoundShaderState.VertexShaderRHI != nullptr);
#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
check(Init.BoundShaderState.GetGeometryShader() == nullptr);
#endif
}
static bool bAllowMSAA = AllowMSAA();
uint16 NumSamples = bAllowMSAA ? FMath::Max(Init.NumSamples, (uint16)1u) : (uint16)1u;
if constexpr(bIsRenderPipelineDesc)
{
RenderPipelineDesc->setSampleCount(NumSamples);
}
else
{
RenderPipelineDesc->setRasterSampleCount(NumSamples);
}
RenderPipelineDesc->setAlphaToCoverageEnabled(NumSamples > 1 && BlendState->bUseAlphaToCoverage);
#if PLATFORM_MAC
if constexpr(bIsRenderPipelineDesc)
{
RenderPipelineDesc->setInputPrimitiveTopology(TranslatePrimitiveTopology(Init.PrimitiveType));
}
#endif
if (Device.SupportsFeature(EMetalFeaturesPipelineBufferMutability))
{
if constexpr(bIsRenderPipelineDesc)
{
FMetalVertexShader* VertexShader = (FMetalVertexShader*)Init.BoundShaderState.VertexShaderRHI;
MTL::PipelineBufferDescriptorArray* VertexPipelineBuffers = RenderPipelineDesc->vertexBuffers();
FMetalShaderBindings& VertexBindings = VertexShader->Bindings;
int8 VertexSideTable = VertexShader->SideTableBinding;
{
uint32 ImmutableBuffers = VertexBindings.ConstantBuffers | VertexBindings.ArgumentBuffers;
while(ImmutableBuffers)
{
uint32 Index = __builtin_ctz(ImmutableBuffers);
ImmutableBuffers &= ~(1 << Index);
if (Index < ML_MaxBuffers)
{
MTL::PipelineBufferDescriptor* PipelineBuffer = VertexPipelineBuffers->object(Index);
PipelineBuffer->setMutability(MTL::MutabilityImmutable);
}
}
if (VertexSideTable > 0)
{
MTL::PipelineBufferDescriptor* PipelineBuffer = VertexPipelineBuffers->object(VertexSideTable);
PipelineBuffer->setMutability(MTL::MutabilityImmutable);
}
}
}
if (PixelShader)
{
MTL::PipelineBufferDescriptorArray* FragmentPipelineBuffers = RenderPipelineDesc->fragmentBuffers();
uint32 ImmutableBuffers = PixelShader->Bindings.ConstantBuffers | PixelShader->Bindings.ArgumentBuffers;
while(ImmutableBuffers)
{
uint32 Index = __builtin_ctz(ImmutableBuffers);
ImmutableBuffers &= ~(1 << Index);
if (Index < ML_MaxBuffers)
{
MTL::PipelineBufferDescriptor* PipelineBuffer = FragmentPipelineBuffers->object(Index);
PipelineBuffer->setMutability(MTL::MutabilityImmutable);
}
}
if (PixelShader->SideTableBinding > 0)
{
MTL::PipelineBufferDescriptor* PipelineBuffer = FragmentPipelineBuffers->object(PixelShader->SideTableBinding);
PipelineBuffer->setMutability(MTL::MutabilityImmutable);
}
}
}
return true;
}
/*
* PSO Harvesting and Reuse
*
* Usage:
*
* To Harvest, run the game with -MetalPSOCache=recreate
* All Render and Compute PSOs created will be harvested into the MTLBinaryArchive
* Console command r.Metal.ShaderPipelineCache.Save will trigger the serialization to file.
* The binary archive's location will be printed to the log.
*
* To reuse, run the game with -MetalPSOCache=use
* The binary archive will be opened from the saved location.
* The binary archive can be moved to another device, as long as it's the same GPU
* and OS build.
*
*/
enum class CacheMode
{
Uninitialized,
Recreate,
Append,
Use,
Ignore
};
static CacheMode GPSOCacheMode = CacheMode::Uninitialized;
static MTL::BinaryArchive* GPSOBinaryArchive = nullptr;
static uint32_t GPSOHarvestCount = 0;
static NS::URL* PipelineCacheSaveLocation()
{
NSString* dir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
if (!dir)
{
UE_LOG(LogMetal, Error, TEXT("Metal Pipeline Cache: Unable to find Documents folder"));
}
NSString* path = [dir stringByAppendingString : @"/mtlarchive.bin"];
NS::URL* NativeURL = NS::URL::fileURLWithPath(NS::String::string(path.UTF8String, NS::UTF8StringEncoding));
return NativeURL;
}
static void InitializeMetalPipelineCache(FMetalDevice& Device)
{
FString strCacheMode;
FParse::Value(FCommandLine::Get(), TEXT("MetalPSOCache="), strCacheMode);
if (strCacheMode.Compare(TEXT("recreate"), ESearchCase::IgnoreCase) == 0)
{
GPSOCacheMode = CacheMode::Recreate;
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: recreate PSO cache"));
}
else if (strCacheMode.Compare(TEXT("append"), ESearchCase::IgnoreCase) == 0)
{
GPSOCacheMode = CacheMode::Append;
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: append to PSO cache"));
}
else if (strCacheMode.Compare(TEXT("use"), ESearchCase::IgnoreCase) == 0)
{
GPSOCacheMode = CacheMode::Use;
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: use PSO cache"));
}
else
{
GPSOCacheMode = CacheMode::Ignore;
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: ignore PSO cache"));
}
if (GPSOCacheMode != CacheMode::Ignore)
{
NS::URL* url = PipelineCacheSaveLocation();
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: pso cache save location will be: %s"), *FString(url->fileSystemRepresentation()));
MTL::BinaryArchiveDescriptor* archDesc = MTL::BinaryArchiveDescriptor::alloc()->init();
check(archDesc);
archDesc->setUrl(((GPSOCacheMode == CacheMode::Append) || (GPSOCacheMode == CacheMode::Use)) ? url : nullptr);
MTL::Device* MTLDevice = Device.GetDevice();
NS::Error * err = nullptr;
GPSOBinaryArchive = MTLDevice->newBinaryArchive(archDesc, &err);
if (err)
{
UE_LOG(LogMetal, Error, TEXT("Error adding Pipeline Functions to Binary Archive: %s"), *FString(url->fileSystemRepresentation()));
}
archDesc->release();
}
}
static void RelatePipelineStateToCache(const MTL::RenderPipelineDescriptor* PipelineDesc, NS::UInteger * outPipelineOpts)
{
if (GPSOBinaryArchive && (GPSOCacheMode != CacheMode::Ignore))
{
if (GPSOCacheMode == CacheMode::Recreate || GPSOCacheMode == CacheMode::Append)
{
NS::Error * err = nullptr;
bool bAddedBinaryPSO = GPSOBinaryArchive->addRenderPipelineFunctions(PipelineDesc, &err);
if (err)
{
UE_LOG(LogMetal, Warning, TEXT("Metal Pipeline Cache: Error adding Pipeline Functions to Binary Archive: %s"), *NSStringToFString(err->localizedDescription()));
}
else if(bAddedBinaryPSO)
{
GPSOHarvestCount++;
if(GMetalBinaryCacheDebugOutput && GPSOHarvestCount % 100)
{
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: Harvested PSO count: %d"), GPSOHarvestCount);
}
}
}
}
}
static void RelatePipelineStateToCache(const MTL::ComputePipelineDescriptor* PipelineDesc, NS::UInteger * outPipelineOpts)
{
if (GPSOBinaryArchive && (GPSOCacheMode != CacheMode::Ignore))
{
if (GPSOCacheMode == CacheMode::Recreate || GPSOCacheMode == CacheMode::Append)
{
NS::Error* err = nullptr;
bool bAddedBinaryPSO = GPSOBinaryArchive->addComputePipelineFunctions(PipelineDesc, &err);
if (err)
{
UE_LOG(LogMetal, Warning, TEXT("Metal Pipeline Cache: Error adding Pipeline Functions to Binary Archive: %s"), *NSStringToFString(err->localizedDescription()));
}
else if(bAddedBinaryPSO)
{
GPSOHarvestCount++;
if(GMetalBinaryCacheDebugOutput && GPSOHarvestCount % 100)
{
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: Harvested PSO count: %d"), GPSOHarvestCount);
}
}
}
}
}
void MetalConsoleCommandSavePipelineFileCache()
{
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: requesting PSO save..."));
{
if (GPSOBinaryArchive && (GPSOCacheMode == CacheMode::Recreate || GPSOCacheMode == CacheMode::Append))
{
NS::URL* url = PipelineCacheSaveLocation();
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: Serialize harvested PSOs to: %s"), *FString(url->fileSystemRepresentation()));
UE_LOG(LogMetal, Log, TEXT("Metal Pipeline Cache: Serialized PSO Count: %d"), GPSOHarvestCount);
NS::Error* err = nullptr;
GPSOBinaryArchive->serializeToURL(url, &err);
if (err)
{
UE_LOG(LogMetal, Error, TEXT("Metal Pipeline Cache: Error Serializing binary archive: %s"), *NSStringToFString(err->localizedDescription()));
}
}
}
}
static FAutoConsoleCommand SavePipelineCacheCmd(
TEXT("rhi.Metal.ShaderPipelineCache.Save"),
TEXT("Save the current pipeline file cache."),
FConsoleCommandDelegate::CreateStatic(MetalConsoleCommandSavePipelineFileCache));
static FMetalShaderPipelinePtr CreateMTLRenderPipeline(FMetalDevice& Device, bool const bSync, FMetalGraphicsPipelineKey const& Key, const FGraphicsPipelineStateInitializer& Init, FMetalGraphicsPipelineState const* State)
{
if (GPSOCacheMode == CacheMode::Uninitialized)
{
InitializeMetalPipelineCache(Device);
}
FMetalVertexShader* VertexShader = (FMetalVertexShader*)Init.BoundShaderState.VertexShaderRHI;
FMetalPixelShader* PixelShader = (FMetalPixelShader*)Init.BoundShaderState.PixelShaderRHI;
MTLFunctionPtr vertexFunction = VertexShader ? VertexShader->GetFunction() : MTLFunctionPtr();
MTLFunctionPtr fragmentFunction = PixelShader ? PixelShader->GetFunction() : MTLFunctionPtr();
#if PLATFORM_SUPPORTS_MESH_SHADERS
FMetalMeshShader* MeshShader = (FMetalMeshShader*)Init.BoundShaderState.GetMeshShader();
FMetalAmplificationShader* AmplificationShader = (FMetalAmplificationShader*)Init.BoundShaderState.GetAmplificationShader();
MTLFunctionPtr meshFunction = MeshShader ? MeshShader->GetFunction() : MTLFunctionPtr();
MTLFunctionPtr amplificationFunction = AmplificationShader ? AmplificationShader->GetFunction() : MTLFunctionPtr();
#endif
#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
FMetalGeometryShader* GeometryShader = (FMetalGeometryShader*)Init.BoundShaderState.GetGeometryShader();
MTLFunctionPtr geometryFunction = GeometryShader ? GeometryShader->GetFunction() : MTLFunctionPtr();
if (geometryFunction)
{
vertexFunction = VertexShader->GetObjectFunctionForGeometryEmulation();
}
#endif
FMetalShaderPipeline* Pipeline = nullptr;
if (vertexFunction && ((PixelShader != nullptr) == (fragmentFunction.get() != nullptr))
#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS
&& geometryFunction == MTLFunctionPtr()
#endif
)
{
NS::Error* Error = nullptr;
MTL::Device* MetalDevice = Device.GetDevice();
uint32 const NumActiveTargets = Init.ComputeNumValidRenderTargets();
check(NumActiveTargets <= MaxSimultaneousRenderTargets);
Pipeline = new FMetalShaderPipeline(Device);
METAL_DEBUG_OPTION(FMemory::Memzero(Pipeline->ResourceMask, sizeof(Pipeline->ResourceMask)));
MTLRenderPipelineDescriptorPtr RenderPipelineDesc = NS::TransferPtr(MTL::RenderPipelineDescriptor::alloc()->init());
check(RenderPipelineDesc);
if ((GPSOCacheMode == CacheMode::Append) || (GPSOCacheMode == CacheMode::Use))
{
RenderPipelineDesc->setBinaryArchives(NS::Array::array(GPSOBinaryArchive));
}
MTLComputePipelineDescriptorPtr ComputePipelineDesc;
if (!ConfigureRenderPipelineDescriptor(Device, RenderPipelineDesc.get(), Key, Init))
{
return nullptr;
}
FMetalVertexDeclaration* VertexDecl = (FMetalVertexDeclaration*)Init.BoundShaderState.VertexDeclarationRHI;
MTLVertexDescriptorPtr MaskedVertexDesc = GetMaskedVertexDescriptor(VertexDecl->Layout.VertexDesc, VertexShader->Bindings.InOutMask);
if(!IsMetalBindlessEnabled())
{
RenderPipelineDesc->setVertexDescriptor(MaskedVertexDesc.get());
}
#if METAL_USE_METAL_SHADER_CONVERTER
else
{
// Create and link stagein function.
if (State->StageInFunctionBytecode.Num() > 0)
{
dispatch_data_t LibraryData = dispatch_data_create(State->StageInFunctionBytecode.GetData(), State->StageInFunctionBytecode.Num(), nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
NS::Error* error = nullptr;
MTLLibraryPtr StageInLib = NS::TransferPtr(MetalDevice->newLibrary(LibraryData, &error));
MTL::LinkedFunctions* StageInFunction = MTL::LinkedFunctions::alloc()->init();
StageInFunction->setFunctions(NS::Array::array(
StageInLib->newFunction(NS::String::string("irconverter_stage_in_shader", NS::UTF8StringEncoding))
));
RenderPipelineDesc->setVertexLinkedFunctions(StageInFunction);
}
}
#endif
RenderPipelineDesc->setVertexFunction(vertexFunction.get());
RenderPipelineDesc->setFragmentFunction(fragmentFunction.get());
#if ENABLE_METAL_GPUPROFILE
NS::String* VertexName = vertexFunction->name();
NS::String* FragmentName = fragmentFunction ? fragmentFunction->name() : NS::String::string("", NS::UTF8StringEncoding);
FString Label = FString::Printf(TEXT("%s+%s"), *NSStringToFString(VertexName), *NSStringToFString(FragmentName));
RenderPipelineDesc->setLabel(FStringToNSString(Label));
#endif
NS::UInteger RenderOption = MTL::PipelineOptionNone;
if (Device.GetRuntimeDebuggingLevel() >= EMetalDebugLevelFastValidation)
{
RenderOption = MTL::PipelineOptionArgumentInfo | MTL::PipelineOptionBufferTypeInfo;
}
{
METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("NewRenderPipeline: %s"), TEXT("")/**FString([RenderPipelineDesc.GetPtr() description])*/)));
RelatePipelineStateToCache(RenderPipelineDesc.get(), &RenderOption);
#if METAL_DEBUG_OPTIONS
Pipeline->RenderDesc = RenderPipelineDesc;
#endif
MTL::RenderPipelineReflection* Reflection = nullptr;
Pipeline->RenderPipelineState = NS::TransferPtr(MetalDevice->newRenderPipelineState(RenderPipelineDesc.get(), (MTL::PipelineOption)RenderOption, &Reflection, &Error));
if(Reflection)
{
Pipeline->RenderPipelineReflection = NS::RetainPtr(Reflection);
}
}
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Failed to generate a pipeline state object: %s"), *NSStringToFString(Error->description()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Vertex shader: %s"), *NSStringToFString(VertexShader->GetSourceCode()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Pixel shader: %s"), PixelShader ? *NSStringToFString(PixelShader->GetSourceCode()) : TEXT("NULL"));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Descriptor: %s"), *NSStringToFString(RenderPipelineDesc->description()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Failed to generate a render pipeline state object:\n\n %s\n\n"), *NSStringToFString(Error->localizedDescription()));
// We need to pass a failure up the chain, so we'll clean up here.
if(Pipeline->RenderPipelineState.get() == nullptr)
{
delete Pipeline;
return nullptr;
}
#if METAL_DEBUG_OPTIONS
Pipeline->VertexSource = VertexShader->GetSourceCode();
Pipeline->VertexSource->retain();
Pipeline->FragmentSource = PixelShader ? PixelShader->GetSourceCode() : nullptr;
if(Pipeline->FragmentSource)
{
Pipeline->FragmentSource->retain();
}
#endif
#if METAL_DEBUG_OPTIONS
if (GFrameCounter > 3)
{
UE_LOG(LogMetal, Verbose, TEXT("Created a hitchy pipeline state for hash %llx %llx %llx"), (uint64)Key.RenderPipelineHash.RasterBits, (uint64)(Key.RenderPipelineHash.TargetBits), (uint64)Key.VertexDescriptorHash.VertexDescHash);
}
#endif
}
#if PLATFORM_SUPPORTS_GEOMETRY_SHADERS // TODO: Merge Geometry Emulation/Mesh Shader paths
else if (vertexFunction && geometryFunction)
{
NS::Error* Error;
MTL::Device* MetalDevice = Device.GetDevice();
uint32 const NumActiveTargets = Init.ComputeNumValidRenderTargets();
check(NumActiveTargets <= MaxSimultaneousRenderTargets);
Pipeline = new FMetalShaderPipeline(Device);
METAL_DEBUG_OPTION(FMemory::Memzero(Pipeline->ResourceMask, sizeof(Pipeline->ResourceMask)));
MTLMeshRenderPipelineDescriptorPtr MeshPipelineDesc = NS::TransferPtr(MTL::MeshRenderPipelineDescriptor::alloc()->init());
MeshPipelineDesc->setObjectFunction(vertexFunction.get());
MeshPipelineDesc->setMeshFunction(geometryFunction.get());
MeshPipelineDesc->setFragmentFunction(fragmentFunction.get());
if (!ConfigureRenderPipelineDescriptor(Device, MeshPipelineDesc.get(), Key, Init))
{
delete Pipeline;
return nullptr;
}
// Create and link stagein function.
if (State->StageInFunctionBytecode.Num() > 0)
{
dispatch_data_t LibraryData = dispatch_data_create(State->StageInFunctionBytecode.GetData(), State->StageInFunctionBytecode.Num(), nullptr, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
MTLLibraryPtr StageInLib = NS::RetainPtr(MetalDevice->newLibrary(LibraryData, nullptr));
MTL::LinkedFunctions* StageInFunction = MTL::LinkedFunctions::alloc()->init();
StageInFunction->setFunctions(NS::Array::array(
StageInLib->newFunction(NS::String::string("irconverter_stage_in_shader", NS::UTF8StringEncoding))
));
MeshPipelineDesc->setObjectLinkedFunctions(StageInFunction);
}
#if ENABLE_METAL_GPUPROFILE
NS::String* MeshName = geometryFunction->name();
NS::String* AmplificationName = vertexFunction->name();
NS::String* FragmentName = fragmentFunction ? fragmentFunction->name() : NS::String::string();
FString LabelName = FString::Printf(TEXT("%s+%s+%s"), *NSStringToFString(MeshName), *NSStringToFString(AmplificationName), *NSStringToFString(FragmentName));
MeshPipelineDesc->setLabel(FStringToNSString(LabelName));
#endif
NS::UInteger RenderOption = MTL::PipelineOptionNone;
MTL::RenderPipelineReflection* Reflection = nullptr;
if (Device.GetRuntimeDebuggingLevel() >= EMetalDebugLevelFastValidation)
{
RenderOption = MTL::PipelineOptionArgumentInfo | MTL::PipelineOptionBufferTypeInfo;
}
{
NS::Error* RenderError = nullptr;
METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("NewMeshRenderPipeline: %s"), TEXT("")/**FString([RenderPipelineDesc.GetPtr() description])*/)));
#if 0
// Binary Archive does not support Mesh shaders...
RelatePipelineStateToCache(MeshPipelineDesc, &RenderOption);
#endif
Pipeline->RenderPipelineState = NS::TransferPtr(MetalDevice->newRenderPipelineState(MeshPipelineDesc.get(), (MTL::PipelineOption)RenderOption, &Reflection, &RenderError));
if (Reflection)
{
Pipeline->RenderPipelineReflection = NS::RetainPtr(Reflection);
#if METAL_DEBUG_OPTIONS
Pipeline->MeshRenderDesc = MeshPipelineDesc;
#endif
}
Error = RenderError;
}
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Failed to generate a pipeline state object: %s"), *NSStringToFString(Error->description()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Mesh shader: %s"), *NSStringToFString(GeometryShader->GetSourceCode()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Object shader: %s"), *NSStringToFString(VertexShader->GetSourceCode()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Pixel shader: %s"), PixelShader ? *NSStringToFString(PixelShader->GetSourceCode()) : TEXT("NULL"));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Descriptor: %s"), *NSStringToFString(MeshPipelineDesc->description()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Failed to generate a mesh render pipeline state object:\n\n %s\n\n"), *NSStringToFString(Error->localizedDescription()));
// We need to pass a failure up the chain, so we'll clean up here.
if(Pipeline->RenderPipelineState.get() == nullptr)
{
delete Pipeline;
return nullptr;
}
#if METAL_DEBUG_OPTIONS
Pipeline->MeshSource = MeshShader ? MeshShader->GetSourceCode() : GeometryShader->GetSourceCode();
Pipeline->ObjectSource = AmplificationShader ? AmplificationShader->GetSourceCode() : nil;
Pipeline->FragmentSource = PixelShader ? PixelShader->GetSourceCode() : nil;
#endif
}
#endif
#if PLATFORM_SUPPORTS_MESH_SHADERS
else if (meshFunction)
{
NS::Error* Error;
MTL::Device* MetalDevice = Device.GetDevice();
uint32 const NumActiveTargets = Init.ComputeNumValidRenderTargets();
check(NumActiveTargets <= MaxSimultaneousRenderTargets);
Pipeline = new FMetalShaderPipeline(Device);
METAL_DEBUG_OPTION(FMemory::Memzero(Pipeline->ResourceMask, sizeof(Pipeline->ResourceMask)));
MTLMeshRenderPipelineDescriptorPtr MeshPipelineDesc = NS::TransferPtr(MTL::MeshRenderPipelineDescriptor::alloc()->init());
MeshPipelineDesc->setObjectFunction(amplificationFunction.get());
MeshPipelineDesc->setMeshFunction(meshFunction.get());
MeshPipelineDesc->setFragmentFunction(fragmentFunction.get());
if (!ConfigureRenderPipelineDescriptor(Device, MeshPipelineDesc.get(), Key, Init))
{
delete Pipeline;
return nullptr;
}
#if ENABLE_METAL_GPUPROFILE
NS::String* MeshName = meshFunction->name();
NS::String* AmplificationName = amplificationFunction ? amplificationFunction->name() : NS::String::string();
NS::String* FragmentName = fragmentFunction ? fragmentFunction->name() : NS::String::string();
FString LabelName = FString::Printf(TEXT("%s+%s+%s"), *NSStringToFString(MeshName), *NSStringToFString(AmplificationName), *NSStringToFString(FragmentName));
MeshPipelineDesc->setLabel(FStringToNSString(LabelName));
#endif
NS::UInteger RenderOption = MTL::PipelineOptionNone;
MTL::RenderPipelineReflection* Reflection = nullptr;
if (Device.GetRuntimeDebuggingLevel() >= EMetalDebugLevelFastValidation)
{
RenderOption = MTL::PipelineOptionArgumentInfo | MTL::PipelineOptionBufferTypeInfo;
}
{
NS::Error* RenderError;
METAL_GPUPROFILE(FScopedMetalCPUStats CPUStat(FString::Printf(TEXT("NewMeshRenderPipeline: %s"), TEXT("")/**FString([RenderPipelineDesc.GetPtr() description])*/)));
#if 0
// Binary Archive does not support Mesh shaders...
RelatePipelineStateToCache(MeshPipelineDesc, &RenderOption);
#endif
Pipeline->RenderPipelineState = NS::TransferPtr(MetalDevice->newRenderPipelineState(MeshPipelineDesc.get(),
(MTL::PipelineOption)RenderOption, &Reflection, &RenderError));
if (Reflection)
{
Pipeline->RenderPipelineReflection = NS::RetainPtr(Reflection);
#if METAL_DEBUG_OPTIONS
Pipeline->MeshRenderDesc = MeshPipelineDesc;
#endif
}
Error = RenderError;
}
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Failed to generate a pipeline state object: %s"), *NSStringToFString(Error->description()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Mesh shader: %s"), *NSStringToFString(MeshShader->GetSourceCode()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Object shader: %s"), AmplificationShader ? *NSStringToFString(AmplificationShader->GetSourceCode()) : TEXT("NULL"));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Pixel shader: %s"), PixelShader ? *NSStringToFString(PixelShader->GetSourceCode()) : TEXT("NULL"));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Descriptor: %s"), *NSStringToFString(MeshPipelineDesc->description()));
UE_CLOG((Pipeline->RenderPipelineState.get() == nullptr), LogMetal, Error, TEXT("Failed to generate a mesh render pipeline state object:\n\n %s\n\n"), *NSStringToFString(Error->localizedDescription()));
// We need to pass a failure up the chain, so we'll clean up here.
if(Pipeline->RenderPipelineState.get() == nullptr)
{
delete Pipeline;
return nil;
}
#if METAL_DEBUG_OPTIONS
Pipeline->MeshSource = MeshShader->GetSourceCode();
Pipeline->ObjectSource = AmplificationShader ? AmplificationShader->GetSourceCode() : nil;
Pipeline->FragmentSource = PixelShader ? PixelShader->GetSourceCode() : nil;
#endif
}
#endif
else
{
checkNoEntry();
}
if (Pipeline && Device.GetRuntimeDebuggingLevel() >= EMetalDebugLevelFastValidation)
{
Pipeline->InitResourceMask();
}
return !bSync ? nullptr : FMetalShaderPipelinePtr(Pipeline);
}
FMetalShaderPipelinePtr GetMTLRenderPipeline(bool const bSync, FMetalGraphicsPipelineState const* State, const FGraphicsPipelineStateInitializer& Init)
{
return FMetalShaderPipelineCache::Get().GetRenderPipeline(bSync, State, Init);
}
void ReleaseMTLRenderPipeline(FMetalShaderPipelinePtr Pipeline)
{
FMetalShaderPipelineCache::Get().ReleaseRenderPipeline(Pipeline);
}
FMetalPipelineStateCacheManager::FMetalPipelineStateCacheManager(FMetalDevice& Device)
{
FMetalShaderPipelineCache::Create(Device);
#if PLATFORM_IOS
OnShaderPipelineCachePreOpenDelegate = FShaderPipelineCache::GetCachePreOpenDelegate().AddRaw(this, &FMetalPipelineStateCacheManager::OnShaderPipelineCachePreOpen);
OnShaderPipelineCacheOpenedDelegate = FShaderPipelineCache::GetCacheOpenedDelegate().AddRaw(this, &FMetalPipelineStateCacheManager::OnShaderPipelineCacheOpened);
OnShaderPipelineCachePrecompilationCompleteDelegate = FShaderPipelineCache::GetPrecompilationCompleteDelegate().AddRaw(this, &FMetalPipelineStateCacheManager::OnShaderPipelineCachePrecompilationComplete);
#endif
}
FMetalPipelineStateCacheManager::~FMetalPipelineStateCacheManager()
{
if (OnShaderPipelineCacheOpenedDelegate.IsValid())
{
FShaderPipelineCache::GetCacheOpenedDelegate().Remove(OnShaderPipelineCacheOpenedDelegate);
}
if (OnShaderPipelineCachePrecompilationCompleteDelegate.IsValid())
{
FShaderPipelineCache::GetPrecompilationCompleteDelegate().Remove(OnShaderPipelineCachePrecompilationCompleteDelegate);
}
}
void FMetalPipelineStateCacheManager::OnShaderPipelineCachePreOpen(FString const& Name, EShaderPlatform Platform, bool& bReady)
{
// only do this when haven't gotten a full pso cache already
struct stat FileInfo;
static FString PrivateWritePathBase = FString([NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]) + TEXT("/");
FString Result = PrivateWritePathBase + FString([NSString stringWithFormat:@"/Caches/%@/com.apple.metal/functions.data", [NSBundle mainBundle].bundleIdentifier]);
FString Result2 = PrivateWritePathBase + FString([NSString stringWithFormat:@"/Caches/%@/com.apple.metal/usecache.txt", [NSBundle mainBundle].bundleIdentifier]);
if (stat(TCHAR_TO_UTF8(*Result), &FileInfo) != -1 && ((FileInfo.st_size / 1024 / 1024) > GMetalCacheMinSize) && stat(TCHAR_TO_UTF8(*Result2), &FileInfo) != -1)
{
bReady = false;
FShaderPipelineCache::SetBatchMode(FShaderPipelineCache::BatchMode::Background);
}
else
{
bReady = true;
FShaderPipelineCache::SetBatchMode(FShaderPipelineCache::BatchMode::Precompile);
}
}
void FMetalPipelineStateCacheManager::OnShaderPipelineCacheOpened(FString const& Name, EShaderPlatform Platform, uint32 Count, const FGuid& VersionGuid, FShaderPipelineCache::FShaderCachePrecompileContext& ShaderCachePrecompileContext)
{
ShaderCachePrecompileContext.SetPrecompilationIsSlowTask();
}
void FMetalPipelineStateCacheManager::OnShaderPipelineCachePrecompilationComplete(uint32 Count, double Seconds, const FShaderPipelineCache::FShaderCachePrecompileContext& ShaderCachePrecompileContext)
{
// Want to ignore any subsequent Shader Pipeline Cache opening/closing, eg when loading modules
FShaderPipelineCache::GetCachePreOpenDelegate().Remove(OnShaderPipelineCachePreOpenDelegate);
FShaderPipelineCache::GetCacheOpenedDelegate().Remove(OnShaderPipelineCacheOpenedDelegate);
FShaderPipelineCache::GetPrecompilationCompleteDelegate().Remove(OnShaderPipelineCachePrecompilationCompleteDelegate);
OnShaderPipelineCachePreOpenDelegate.Reset();
OnShaderPipelineCacheOpenedDelegate.Reset();
OnShaderPipelineCachePrecompilationCompleteDelegate.Reset();
}