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

219 lines
8.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MetalCommandList.cpp: Metal command buffer list wrapper.
=============================================================================*/
#include "MetalCommandList.h"
#include "MetalRHIPrivate.h"
#include "MetalShaderTypes.h"
#include "MetalGraphicsPipelineState.h"
#include "MetalCommandQueue.h"
#include "MetalProfiler.h"
#include "MetalCommandBuffer.h"
#pragma mark - Public C++ Boilerplate -
#if PLATFORM_IOS
extern bool GIsSuspended;
#endif
FMetalCommandList::FMetalCommandList(FMetalCommandQueue& InCommandQueue)
: CommandQueue(InCommandQueue)
{}
FMetalCommandList::~FMetalCommandList(void)
{
}
#pragma mark - Public Command List Mutators -
static const TCHAR* StringFromCommandEncoderError(MTL::CommandEncoderErrorState ErrorState)
{
switch (ErrorState)
{
case MTL::CommandEncoderErrorStateUnknown: return TEXT("Unknown");
case MTL::CommandEncoderErrorStateAffected: return TEXT("Affected");
case MTL::CommandEncoderErrorStateCompleted: return TEXT("Completed");
case MTL::CommandEncoderErrorStateFaulted: return TEXT("Faulted");
case MTL::CommandEncoderErrorStatePending: return TEXT("Pending");
}
return TEXT("Unknown");
}
extern CORE_API bool GIsGPUCrashed;
static void ReportMetalCommandBufferFailure(MTL::CommandBuffer* CompletedBuffer, TCHAR const* ErrorType, bool bDoCheck=true)
{
GIsGPUCrashed = true;
NS::String* Label = CompletedBuffer->label();
int32 Code = CompletedBuffer->error()->code();
NS::String* Domain = CompletedBuffer->error()->domain();
NS::String* ErrorDesc = CompletedBuffer->error()->localizedDescription();
NS::String* FailureDesc = CompletedBuffer->error()->localizedFailureReason();
NS::String* RecoveryDesc = CompletedBuffer->error()->localizedRecoverySuggestion();
FString LabelString = Label ? FString(Label->cString(NS::UTF8StringEncoding)) : FString(TEXT("Unknown"));
FString DomainString = Domain ? FString(Domain->cString(NS::UTF8StringEncoding)) : FString(TEXT("Unknown"));
FString ErrorString = ErrorDesc ? FString(ErrorDesc->cString(NS::UTF8StringEncoding)) : FString(TEXT("Unknown"));
FString FailureString = FailureDesc ? FString(FailureDesc->cString(NS::UTF8StringEncoding)) : FString(TEXT("Unknown"));
FString RecoveryString = RecoveryDesc ? FString(RecoveryDesc->cString(NS::UTF8StringEncoding)) : FString(TEXT("Unknown"));
NS::String* Desc = CompletedBuffer->debugDescription();
UE_LOG(LogMetal, Warning, TEXT("Metal Command Buffer Failure: %s, %s"), ErrorType, *FString(Desc->cString(NS::UTF8StringEncoding)));
#if PLATFORM_IOS
if (bDoCheck && !GIsSuspended)
#endif
{
// Dump GPU fault information for the GPU encoders
if (&MTLCommandBufferEncoderInfoErrorKey != nullptr)
{
NS::Dictionary* ErrorDict = CompletedBuffer->error()->userInfo();
NS::Array* EncoderInfoArray = (NS::Array*)ErrorDict->object(MTL::CommandBufferEncoderInfoErrorKey);
if (EncoderInfoArray)
{
UE_LOG(LogMetal, Warning, TEXT("GPU Encoder Crash Info:"));
for(uint32 Idx = 0; Idx < EncoderInfoArray->count(); ++Idx)
{
MTL::CommandBufferEncoderInfo* EncoderInfo = (MTL::CommandBufferEncoderInfo*)EncoderInfoArray->object(Idx);
UE_LOG(LogMetal, Warning, TEXT("MTLCommandBufferEncoder - Label: %s, State: %s"), *NSStringToFString(EncoderInfo->label()), StringFromCommandEncoderError(EncoderInfo->errorState()));
NS::Array* SignPosts = EncoderInfo->debugSignposts();
if (SignPosts->count() > 0)
{
UE_LOG(LogMetal, Warning, TEXT(" Signposts:"));
for (uint32_t SignPostIdx = 0; SignPostIdx < SignPosts->count(); ++SignPostIdx)
{
NS::String* Signpost = (NS::String*)SignPosts->object(SignPostIdx);
UE_LOG(LogMetal, Warning, TEXT(" - %s"), *NSStringToFString(Signpost));
}
}
}
}
}
#if PLATFORM_IOS
UE_LOG(LogMetal, Warning, TEXT("Command Buffer %s Failed with %s Error! Error Domain: %s Code: %d Description %s %s %s"), *LabelString, ErrorType, *DomainString, Code, *ErrorString, *FailureString, *RecoveryString);
FIOSPlatformMisc::GPUAssert();
#else
UE_LOG(LogMetal, Fatal, TEXT("Command Buffer %s Failed with %s Error! Error Domain: %s Code: %d Description %s %s %s"), *LabelString, ErrorType, *DomainString, Code, *ErrorString, *FailureString, *RecoveryString);
#endif
}
}
static __attribute__ ((optnone)) void MetalCommandBufferFailureInternal(MTL::CommandBuffer* CompletedBuffer)
{
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("Internal"));
}
static __attribute__ ((optnone)) void MetalCommandBufferFailureTimeout(MTL::CommandBuffer* CompletedBuffer)
{
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("Timeout"), PLATFORM_IOS);
}
static __attribute__ ((optnone)) void MetalCommandBufferFailurePageFault(MTL::CommandBuffer* CompletedBuffer)
{
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("PageFault"));
}
static __attribute__ ((optnone)) void MetalCommandBufferFailureAccessRevoked(MTL::CommandBuffer* CompletedBuffer)
{
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("AccessRevoked"));
}
static __attribute__ ((optnone)) void MetalCommandBufferFailureNotPermitted(MTL::CommandBuffer* CompletedBuffer)
{
// when iOS goes into the background, it can get a delayed NotPermitted error, so we can't crash in this case, just allow it to not be submitted
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("NotPermitted"), !PLATFORM_IOS);
}
static __attribute__ ((optnone)) void MetalCommandBufferFailureOutOfMemory(MTL::CommandBuffer* CompletedBuffer)
{
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("OutOfMemory"));
}
static __attribute__ ((optnone)) void MetalCommandBufferFailureInvalidResource(MTL::CommandBuffer* CompletedBuffer)
{
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("InvalidResource"));
}
static void HandleMetalCommandBufferError(MTL::CommandBuffer* CompletedBuffer)
{
MTL::CommandBufferError Code = (MTL::CommandBufferError)CompletedBuffer->error()->code();
switch(Code)
{
case MTL::CommandBufferErrorInternal:
MetalCommandBufferFailureInternal(CompletedBuffer);
break;
case MTL::CommandBufferErrorTimeout:
MetalCommandBufferFailureTimeout(CompletedBuffer);
break;
case MTL::CommandBufferErrorPageFault:
MetalCommandBufferFailurePageFault(CompletedBuffer);
break;
case MTL::CommandBufferErrorAccessRevoked:
MetalCommandBufferFailureAccessRevoked(CompletedBuffer);
break;
case MTL::CommandBufferErrorNotPermitted:
MetalCommandBufferFailureNotPermitted(CompletedBuffer);
break;
case MTL::CommandBufferErrorOutOfMemory:
MetalCommandBufferFailureOutOfMemory(CompletedBuffer);
break;
case MTL::CommandBufferErrorInvalidResource:
MetalCommandBufferFailureInvalidResource(CompletedBuffer);
break;
case MTL::CommandBufferErrorNone:
// No error
break;
default:
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("Unknown"));
break;
}
}
static __attribute__ ((optnone)) void HandleAMDMetalCommandBufferError(MTL::CommandBuffer* CompletedBuffer)
{
HandleMetalCommandBufferError(CompletedBuffer);
}
static __attribute__ ((optnone)) void HandleIntelMetalCommandBufferError(MTL::CommandBuffer* CompletedBuffer)
{
HandleMetalCommandBufferError(CompletedBuffer);
}
void FMetalCommandList::HandleMetalCommandBufferFailure(MTL::CommandBuffer* CompletedBuffer)
{
if (CompletedBuffer->error()->domain()->isEqualToString(NS::String::string("MTLCommandBufferErrorDomain", NS::UTF8StringEncoding)))
{
if (GRHIVendorId && IsRHIDeviceAMD())
{
HandleAMDMetalCommandBufferError(CompletedBuffer);
}
else if (GRHIVendorId && IsRHIDeviceIntel())
{
HandleIntelMetalCommandBufferError(CompletedBuffer);
}
else
{
HandleMetalCommandBufferError(CompletedBuffer);
}
}
else
{
ReportMetalCommandBufferFailure(CompletedBuffer, TEXT("Unknown"));
}
}
void FMetalCommandList::FinalizeCommandBuffer(FMetalCommandBuffer* Buffer)
{
#if RHI_NEW_GPU_PROFILER == 0
check(Buffer);
FMetalCommandBufferTimer& Timer = FMetalCommandBufferTimer::GetFrameBufferTimer();
Timer.Submit();
Buffer->SetTimer(&Timer);
#endif
}