Files
UnrealEngineUWP/Engine/Source/Runtime/RenderCore/Private/RenderGraphDumpResources.cpp
guillaume abadie 7118d3fffa Allows to automatically trigger any command (such as DumpGPU) when doing a high resolution screenshot
#rb juan.canada
#jira UE-133882

#ROBOMERGE-AUTHOR: guillaume.abadie
#ROBOMERGE-SOURCE: CL 18299389 via CL 18374100 via CL 18374160
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v895-18170469)

[CL 18374168 by guillaume abadie in ue5-release-engine-test branch]
2021-12-03 17:00:32 -05:00

1769 lines
58 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RenderGraph.h"
#include "HAL/PlatformFileManager.h"
#include "HAL/PlatformMisc.h"
#include "Misc/FileHelper.h"
#include "Misc/WildcardString.h"
#include "Misc/OutputDeviceRedirector.h"
#include "Runtime/Launch/Resources/Version.h"
#include "BuildSettings.h"
#include "Serialization/JsonTypes.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "GenericPlatform/GenericPlatformCrashContext.h"
#include "GenericPlatform/GenericPlatformDriver.h"
#include "RenderGraphUtils.h"
#include "GlobalShader.h"
#include "RHIValidation.h"
#if RDG_DUMP_RESOURCES
namespace
{
static TAutoConsoleVariable<FString> GDumpGPURootCVar(
TEXT("r.DumpGPU.Root"),
TEXT("*"),
TEXT("Allows to filter the tree when using r.DumpGPU command, the pattern match is case sensitive."),
ECVF_Default);
static TAutoConsoleVariable<int32> GDumpTextureCVar(
TEXT("r.DumpGPU.Texture"), 2,
TEXT("Whether to dump textures.\n")
TEXT(" 0: Ignores all textures\n")
TEXT(" 1: Dump only textures' descriptors\n")
TEXT(" 2: Dump textures' descriptors and binaries (default)"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> GDumpBufferCVar(
TEXT("r.DumpGPU.Buffer"), 2,
TEXT("Whether to dump buffer.\n")
TEXT(" 0: Ignores all buffers\n")
TEXT(" 1: Dump only buffers' descriptors\n")
TEXT(" 2: Dump buffers' descriptors and binaries (default)"),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> GDumpGPUPassParameters(
TEXT("r.DumpGPU.PassParameters"), 1,
TEXT("Whether to dump the pass parameters."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> GDumpGPUDraws(
TEXT("r.DumpGPU.Draws"), 0,
TEXT("Whether to dump resource after each individual draw call (disabled by default)."),
ECVF_Default);
static TAutoConsoleVariable<int32> GDumpExploreCVar(
TEXT("r.DumpGPU.Explore"), 1,
TEXT("Whether to open file explorer to where the GPU dump on completion (enabled by default)."),
ECVF_Default);
static TAutoConsoleVariable<int32> GDumpRenderingConsoleVariablesCVar(
TEXT("r.DumpGPU.ConsoleVariables"), 1,
TEXT("Whether to dump rendering console variables (enabled by default)."),
ECVF_Default);
static TAutoConsoleVariable<int32> GDumpTestEnableDiskWrite(
TEXT("r.DumpGPU.Test.EnableDiskWrite"), 1,
TEXT("Master switch whether any files should be written to disk, used for r.DumpGPU automation tests to not fill up workers' hard drive."),
ECVF_Default);
static TAutoConsoleVariable<int32> GDumpTestPrettifyResourceFileNames(
TEXT("r.DumpGPU.Test.PrettifyResourceFileNames"), 0,
TEXT("Whether the resource file names should include resource name. May increase the likelyness of running into Windows' filepath limit."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<FString> GDumpGPUDirectoryCVar(
TEXT("r.DumpGPU.Directory"), TEXT(""),
TEXT("Directory to dump to."),
ECVF_Default);
// Although this cvar does not seams used in the C++ code base, it is dumped by DumpRenderingCVarsToCSV() and used by GPUDumpViewer.html.
static TAutoConsoleVariable<FString> GDumpGPUVisualizeResource(
TEXT("r.DumpGPU.Viewer.Visualize"), TEXT(""),
TEXT("Name of RDG output resource to automatically open in the dump viewer."),
ECVF_Default);
class FDumpTextureCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FDumpTextureCS);
SHADER_USE_PARAMETER_STRUCT(FDumpTextureCS, FGlobalShader);
static inline bool IsSupported(const FStaticShaderPlatform ShaderPlatform)
{
return RHISupportsComputeShaders(ShaderPlatform);
}
enum class ETextureType
{
Texture2DFloatNoMSAA,
Texture2DUintNoMSAA,
Texture2DDepthStencilNoMSAA,
MAX
};
class FTextureTypeDim : SHADER_PERMUTATION_ENUM_CLASS("DIM_TEXTURE_TYPE", ETextureType);
using FPermutationDomain = TShaderPermutationDomain<FTextureTypeDim>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return IsSupported(Parameters.Platform);
}
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_SRV(Texture2D, Texture)
SHADER_PARAMETER_UAV(RWTexture2D, StagingOutput)
END_SHADER_PARAMETER_STRUCT()
};
IMPLEMENT_GLOBAL_SHADER(FDumpTextureCS, "/Engine/Private/Tools/DumpTexture.usf", "MainCS", SF_Compute);
BEGIN_SHADER_PARAMETER_STRUCT(FDumpTexturePass, )
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, Texture)
RDG_TEXTURE_ACCESS_DYNAMIC(TextureAccess)
END_SHADER_PARAMETER_STRUCT()
BEGIN_SHADER_PARAMETER_STRUCT(FDumpBufferPass, )
RDG_BUFFER_ACCESS(Buffer, ERHIAccess::CopySrc)
END_SHADER_PARAMETER_STRUCT()
struct FRDGResourceDumpContext
{
static constexpr const TCHAR* kBaseDir = TEXT("Base/");
static constexpr const TCHAR* kPassesDir = TEXT("Passes/");
static constexpr const TCHAR* kResourcesDir = TEXT("Resources/");
static constexpr const TCHAR* kStructuresDir = TEXT("Structures/");
static constexpr const TCHAR* kStructuresMetadataDir = TEXT("StructuresMetadata/");
bool bEnableDiskWrite = false;
FString DumpingDirectoryPath;
FDateTime Time;
FGenericPlatformMemoryConstants MemoryConstants;
FGenericPlatformMemoryStats MemoryStats;
int32 ResourcesDumpPasses = 0;
int32 ResourcesDumpExecutedPasses = 0;
int32 PassesCount = 0;
TMap<const FRDGResource*, const FRDGPass*> LastResourceVersion;
TSet<const void*> IsDumpedToDisk;
// Pass being dumping individual draws
const FRDGPass* DrawDumpingPass = nullptr;
int32 DrawDumpCount = 0;
bool bShowInExplore = false;
bool IsDumpingFrame() const
{
check(IsInRenderingThread() || IsInParallelRenderingThread());
return !DumpingDirectoryPath.IsEmpty();
}
FString GetDumpFullPath(const FString& DumpRelativeFileName) const
{
check(bEnableDiskWrite);
return DumpingDirectoryPath / DumpRelativeFileName;
}
bool DumpStringToFile(FStringView OutputString, const FString& FileName, uint32 WriteFlags = FILEWRITE_None)
{
// Make it has if the write happened and was successful.
if (!bEnableDiskWrite)
{
return true;
}
FString FullPath = GetDumpFullPath(FileName);
return FFileHelper::SaveStringToFile(
OutputString, *FullPath,
FFileHelper::EEncodingOptions::AutoDetect, &IFileManager::Get(), WriteFlags);
}
bool DumpJsonToFile(const TSharedPtr<FJsonObject>& JsonObject, const FString& FileName, uint32 WriteFlags = FILEWRITE_None)
{
FString OutputString;
TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create(&OutputString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
return DumpStringToFile(OutputString, FileName, WriteFlags);
}
bool DumpBinaryToFile(TArrayView<const uint8> ArrayView, const FString& FileName)
{
// Make it has if the write happened and was successful.
if (!bEnableDiskWrite)
{
return true;
}
FString FullPath = GetDumpFullPath(FileName);
return FFileHelper::SaveArrayToFile(ArrayView, *FullPath);
}
bool DumpBinaryToFile(const TArray64<uint8>& Array, const FString& FileName)
{
// Make it has if the write happened and was successful.
if (!bEnableDiskWrite)
{
return true;
}
FString FullPath = GetDumpFullPath(FileName);
return FFileHelper::SaveArrayToFile(Array, *FullPath);
}
bool IsUnsafeToDumpResource(SIZE_T ResourceByteSize, float DumpMemoryMultiplier) const
{
uint64 AproximatedStagingMemoryRequired = uint64(double(ResourceByteSize) * DumpMemoryMultiplier);
uint64 MaxMemoryAvailable = FMath::Min(MemoryStats.AvailablePhysical, MemoryStats.AvailableVirtual);
return AproximatedStagingMemoryRequired > MaxMemoryAvailable;
}
template<typename T>
static uint64 PtrToUint(const T* Ptr)
{
return static_cast<uint64>(reinterpret_cast<size_t>(Ptr));
}
template<typename T>
static FString PtrToString(const T* Ptr)
{
return FString::Printf(TEXT("%016x"), PtrToUint(Ptr));
}
static FString GetUniqueResourceName(const FRDGResource* Resource)
{
if (GDumpTestPrettifyResourceFileNames.GetValueOnRenderThread())
{
FString UniqueResourceName = FString::Printf(TEXT("%s.%016x"), Resource->Name, PtrToUint(Resource));
UniqueResourceName.ReplaceInline(TEXT("/"), TEXT(""));
UniqueResourceName.ReplaceInline(TEXT("\\"), TEXT(""));
return UniqueResourceName;
}
return PtrToString(Resource);
}
static FString GetUniqueSubResourceName(FRDGTextureSRVDesc SubResourceDesc)
{
check(SubResourceDesc.NumMipLevels == 1);
FString UniqueResourceName = GetUniqueResourceName(SubResourceDesc.Texture);
if (SubResourceDesc.Format == PF_X24_G8)
{
return FString::Printf(TEXT("%s.stencil"), *UniqueResourceName);
}
return FString::Printf(TEXT("%s.mip%d"), *UniqueResourceName, SubResourceDesc.MipLevel);
}
void ReleaseRHIResources(FRHICommandListImmediate& RHICmdList)
{
// Flush the RHI resource memory so the readback memory can be fully reused in the next resource dump.
{
RHICmdList.SubmitCommandsAndFlushGPU();
RHICmdList.BlockUntilGPUIdle();
FRHIResource::FlushPendingDeletes(RHICmdList);
RHICmdList.FlushResources();
RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
}
}
void UpdatePassProgress()
{
ResourcesDumpExecutedPasses++;
if (ResourcesDumpExecutedPasses % 10 == 0)
{
UE_LOG(LogRendererCore, Display, TEXT("Dumped %d / %d resources"), ResourcesDumpExecutedPasses, ResourcesDumpPasses);
}
}
void DumpRenderingCVarsToCSV() const
{
FString FileName = GetDumpFullPath(FString(FRDGResourceDumpContext::kBaseDir) / TEXT("ConsoleVariables.csv"));
TUniquePtr<FArchive> Ar = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*FileName));
auto OnConsoleVariable = [&Ar](const TCHAR* CVarName, IConsoleObject* ConsoleObj)
{
if (ConsoleObj->TestFlags(ECVF_Unregistered))
{
return;
}
const IConsoleVariable* CVar = ConsoleObj->AsVariable();
if (!CVar)
{
return;
}
EConsoleVariableFlags CVarFlags = CVar->GetFlags();
const TCHAR* Type = nullptr;
if (CVar->IsVariableBool())
{
Type = TEXT("bool");
}
else if (CVar->IsVariableInt())
{
Type = TEXT("int32");
}
else if (CVar->IsVariableFloat())
{
Type = TEXT("float");
}
else if (CVar->IsVariableString())
{
Type = TEXT("FString");
}
else
{
return;
}
const TCHAR* SetByName = TEXT("");
switch (CVarFlags & ECVF_SetByMask)
{
case ECVF_SetByConstructor:
{
SetByName = TEXT("Constructor");
break;
}
case ECVF_SetByScalability:
{
SetByName = TEXT("Scalability");
break;
}
case ECVF_SetByGameSetting:
{
SetByName = TEXT("GameSetting");
break;
}
case ECVF_SetByProjectSetting:
{
SetByName = TEXT("ProjectSetting");
break;
}
case ECVF_SetBySystemSettingsIni:
{
SetByName = TEXT("SystemSettingsIni");
break;
}
case ECVF_SetByDeviceProfile:
{
SetByName = TEXT("DeviceProfile");
break;
}
case ECVF_SetByConsoleVariablesIni:
{
SetByName = TEXT("ConsoleVariablesIni");
break;
}
case ECVF_SetByCommandline:
{
SetByName = TEXT("Commandline");
break;
}
case ECVF_SetByCode:
{
SetByName = TEXT("Code");
break;
}
case ECVF_SetByConsole:
{
SetByName = TEXT("Console");
break;
}
default:
unimplemented();
}
FString Value = CVar->GetString();
FString CSVLine = FString::Printf(TEXT("%s,%s,%s,%s\n"), CVarName, Type, SetByName, *Value);
FStringView CSVLineView(CSVLine);
auto Src = StringCast<ANSICHAR>(CSVLineView.GetData(), CSVLineView.Len());
Ar->Serialize((ANSICHAR*)Src.Get(), Src.Length() * sizeof(ANSICHAR));
};
bool bSuccess = false;
if (Ar)
{
{
FString CSVLine = TEXT("CVar,Type,SetBy,Value\n");
FStringView CSVLineView(CSVLine);
auto Src = StringCast<ANSICHAR>(CSVLineView.GetData(), CSVLineView.Len());
Ar->Serialize((ANSICHAR*)Src.Get(), Src.Length() * sizeof(ANSICHAR));
}
if (GDumpRenderingConsoleVariablesCVar.GetValueOnGameThread() != 0)
{
IConsoleManager::Get().ForEachConsoleObjectThatStartsWith(FConsoleObjectVisitor::CreateLambda(OnConsoleVariable), TEXT("r."));
IConsoleManager::Get().ForEachConsoleObjectThatStartsWith(FConsoleObjectVisitor::CreateLambda(OnConsoleVariable), TEXT("sg."));
IConsoleManager::Get().ForEachConsoleObjectThatContains(FConsoleObjectVisitor::CreateLambda(OnConsoleVariable), TEXT("rhi"));
}
else
{
IConsoleManager::Get().ForEachConsoleObjectThatStartsWith(FConsoleObjectVisitor::CreateLambda(OnConsoleVariable), TEXT("r.DumpGPU."));
}
// Always explicitly close to catch errors from flush/close
Ar->Close();
bSuccess = !Ar->IsError() && !Ar->IsCriticalError();
}
if (bSuccess)
{
UE_LOG(LogRendererCore, Display, TEXT("DumpGPU dumped rendering cvars to %s."), *FileName);
}
else
{
UE_LOG(LogRendererCore, Error, TEXT("DumpGPU had a file error when dumping rendering cvars to %s."), *FileName);
}
}
template<typename T>
bool IsDumped(const T* Ptr) const
{
return IsDumpedToDisk.Contains(static_cast<const void*>(Ptr));
}
template<typename T>
void SetDumped(const T* Ptr)
{
check(!IsDumped(Ptr));
if (IsDumpedToDisk.Num() % 1024 == 0)
{
IsDumpedToDisk.Reserve(IsDumpedToDisk.Num() + 1024);
}
IsDumpedToDisk.Add(static_cast<const void*>(Ptr));
}
void GetResourceDumpInfo(
const FRDGPass* Pass,
const FRDGResource* Resource,
bool bIsOutputResource,
bool* bOutDumpResourceInfos,
bool* bOutDumpResourceBinary)
{
*bOutDumpResourceInfos = false;
*bOutDumpResourceBinary = bIsOutputResource;
//if (bIsOutputResource)
//{
// *OutResourceVersionDumpName = GetResourceVersionDumpName(Pass, Resource);
//}
if (!LastResourceVersion.Contains(Resource))
{
// First time we ever see this resource, so dump it's info to disk
*bOutDumpResourceInfos = true;
// If not an output, it might be a resource undumped by r.DumpGPU.Root or external texture so still dump it as v0.
if (!bIsOutputResource)
{
*bOutDumpResourceBinary = true;
}
if (LastResourceVersion.Num() % 1024 == 0)
{
LastResourceVersion.Reserve(LastResourceVersion.Num() + 1024);
}
LastResourceVersion.Add(Resource, Pass);
}
else
{
LastResourceVersion[Resource] = Pass;
}
}
FString ToJson(EPixelFormat Format)
{
FString PixelFormat = GPixelFormats[Format].Name;
if (!PixelFormat.StartsWith(TEXT("PF_")))
{
PixelFormat = FString::Printf(TEXT("PF_%s"), GPixelFormats[Format].Name);
}
return PixelFormat;
}
TSharedPtr<FJsonObject> ToJson(const FShaderParametersMetadata::FMember& Member)
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("Name"), Member.GetName());
JsonObject->SetStringField(TEXT("ShaderType"), Member.GetShaderType());
JsonObject->SetNumberField(TEXT("FileLine"), Member.GetFileLine());
JsonObject->SetNumberField(TEXT("Offset"), Member.GetOffset());
JsonObject->SetStringField(TEXT("BaseType"), GetUniformBufferBaseTypeString(Member.GetBaseType()));
JsonObject->SetNumberField(TEXT("Precision"), Member.GetPrecision());
JsonObject->SetNumberField(TEXT("NumRows"), Member.GetNumRows());
JsonObject->SetNumberField(TEXT("NumColumns"), Member.GetNumColumns());
JsonObject->SetNumberField(TEXT("NumElements"), Member.GetNumElements());
JsonObject->SetStringField(TEXT("StructMetadata"), *PtrToString(Member.GetStructMetadata()));
return JsonObject;
}
TSharedPtr<FJsonObject> ToJson(const FShaderParametersMetadata* Metadata)
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("StructTypeName"), Metadata->GetStructTypeName());
JsonObject->SetStringField(TEXT("ShaderVariableName"), Metadata->GetShaderVariableName());
JsonObject->SetStringField(TEXT("FileName"), Metadata->GetFileName());
JsonObject->SetNumberField(TEXT("FileLine"), Metadata->GetFileLine());
JsonObject->SetNumberField(TEXT("Size"), Metadata->GetSize());
//JsonObject->SetNumberField(TEXT("UseCase"), Metadata->GetUseCase());
{
TArray<TSharedPtr<FJsonValue>> Members;
for (const FShaderParametersMetadata::FMember& Member : Metadata->GetMembers())
{
Members.Add(MakeShared<FJsonValueObject>(ToJson(Member)));
}
JsonObject->SetArrayField(TEXT("Members"), Members);
}
return JsonObject;
}
TSharedPtr<FJsonObject> ToJson(const TCHAR* Name, const FRDGTextureDesc& Desc)
{
FString PixelFormat = ToJson(Desc.Format);
int32 ResourceByteSize = Desc.Extent.X * Desc.Extent.Y * Desc.Depth * Desc.ArraySize * Desc.NumSamples * GPixelFormats[Desc.Format].BlockBytes;
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("Name"), Name);
JsonObject->SetNumberField(TEXT("ByteSize"), ResourceByteSize);
JsonObject->SetStringField(TEXT("Desc"), TEXT("FRDGTextureDesc"));
JsonObject->SetStringField(TEXT("Type"), GetTextureDimensionString(Desc.Dimension));
JsonObject->SetStringField(TEXT("Format"), *PixelFormat);
JsonObject->SetNumberField(TEXT("ExtentX"), Desc.Extent.X);
JsonObject->SetNumberField(TEXT("ExtentY"), Desc.Extent.Y);
JsonObject->SetNumberField(TEXT("Depth"), Desc.Depth);
JsonObject->SetNumberField(TEXT("ArraySize"), Desc.ArraySize);
JsonObject->SetNumberField(TEXT("NumMips"), Desc.NumMips);
JsonObject->SetNumberField(TEXT("NumSamples"), Desc.NumSamples);
{
TArray<TSharedPtr<FJsonValue>> FlagsNames;
for (uint64 BitId = 0; BitId < uint64(8 * sizeof(ETextureCreateFlags)); BitId++)
{
ETextureCreateFlags Flag = ETextureCreateFlags(uint64(1) << BitId);
if (EnumHasAnyFlags(Desc.Flags, Flag))
{
FlagsNames.Add(MakeShareable(new FJsonValueString(GetTextureCreateFlagString(Flag))));
}
}
JsonObject->SetArrayField(TEXT("Flags"), FlagsNames);
}
return JsonObject;
}
TSharedPtr<FJsonObject> ToJson(const TCHAR* Name, const FRDGBufferDesc& Desc, int32 ResourceByteSize)
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("Name"), Name);
JsonObject->SetNumberField(TEXT("ByteSize"), ResourceByteSize);
JsonObject->SetStringField(TEXT("Desc"), TEXT("FRDGBufferDesc"));
JsonObject->SetStringField(TEXT("Type"), GetBufferUnderlyingTypeName(Desc.UnderlyingType));
JsonObject->SetNumberField(TEXT("BytesPerElement"), Desc.BytesPerElement);
JsonObject->SetNumberField(TEXT("NumElements"), Desc.NumElements);
JsonObject->SetStringField(TEXT("Metadata"), *PtrToString(Desc.Metadata));
{
TArray<TSharedPtr<FJsonValue>> UsageNames;
for (uint64 BitId = 0; BitId < uint64(8 * sizeof(EBufferUsageFlags)); BitId++)
{
EBufferUsageFlags Flag = EBufferUsageFlags(uint64(1) << BitId);
if (EnumHasAnyFlags(Desc.Usage, Flag))
{
UsageNames.Add(MakeShareable(new FJsonValueString(GetBufferUsageFlagString(Flag))));
}
}
JsonObject->SetArrayField(TEXT("Usage"), UsageNames);
}
return JsonObject;
}
void Dump(const FShaderParametersMetadata* Metadata)
{
if (IsDumped(Metadata))
{
return;
}
TSharedPtr<FJsonObject> JsonObject = ToJson(Metadata);
FString JsonPath = kStructuresMetadataDir / PtrToString(Metadata) + TEXT(".json");
DumpJsonToFile(JsonObject, JsonPath);
SetDumped(Metadata);
// Dump dependencies
Metadata->IterateStructureMetadataDependencies(
[&](const FShaderParametersMetadata* Struct)
{
if (Struct)
{
Dump(Struct);
}
});
}
struct FTextureSubresourceDumpDesc
{
FIntPoint SubResourceExtent = FIntPoint(0, 0);
SIZE_T ByteSize = 0;
bool bPreprocessForStaging = false;
FDumpTextureCS::ETextureType DumpTextureType = FDumpTextureCS::ETextureType::MAX;
EPixelFormat PreprocessedPixelFormat;
bool IsDumpSupported() const
{
return ByteSize != SIZE_T(0);
}
};
FTextureSubresourceDumpDesc TranslateSubresourceDumpDesc(FRDGTextureSRVDesc SubresourceDesc)
{
const FRDGTextureDesc& Desc = SubresourceDesc.Texture->Desc;
FTextureSubresourceDumpDesc SubresourceDumpDesc;
SubresourceDumpDesc.PreprocessedPixelFormat = Desc.Format;
bool bIsUnsupported = false;
if (GPixelFormats[Desc.Format].BlockSizeX != 1 ||
GPixelFormats[Desc.Format].BlockSizeY != 1 ||
GPixelFormats[Desc.Format].BlockSizeZ != 1)
{
bIsUnsupported = true;
}
if (!bIsUnsupported && Desc.IsTexture2D() && !Desc.IsMultisample() && !Desc.IsTextureArray())
{
SubresourceDumpDesc.SubResourceExtent.X = Desc.Extent.X >> SubresourceDesc.MipLevel;
SubresourceDumpDesc.SubResourceExtent.Y = Desc.Extent.Y >> SubresourceDesc.MipLevel;
if (IsUintFormat(Desc.Format) || IsSintFormat(Desc.Format))
{
SubresourceDumpDesc.DumpTextureType = FDumpTextureCS::ETextureType::Texture2DUintNoMSAA;
}
else
{
SubresourceDumpDesc.DumpTextureType = FDumpTextureCS::ETextureType::Texture2DFloatNoMSAA;
}
if (SubresourceDesc.Format == PF_X24_G8)
{
SubresourceDumpDesc.PreprocessedPixelFormat = PF_R8_UINT;
SubresourceDumpDesc.DumpTextureType = FDumpTextureCS::ETextureType::Texture2DDepthStencilNoMSAA;
}
else if (Desc.Format == PF_DepthStencil)
{
SubresourceDumpDesc.PreprocessedPixelFormat = PF_R32_FLOAT;
SubresourceDumpDesc.DumpTextureType = FDumpTextureCS::ETextureType::Texture2DFloatNoMSAA;
}
else if (Desc.Format == PF_ShadowDepth)
{
SubresourceDumpDesc.PreprocessedPixelFormat = PF_R32_FLOAT;
SubresourceDumpDesc.DumpTextureType = FDumpTextureCS::ETextureType::Texture2DFloatNoMSAA;
}
else if (Desc.Format == PF_D24)
{
SubresourceDumpDesc.PreprocessedPixelFormat = PF_R32_FLOAT;
SubresourceDumpDesc.DumpTextureType = FDumpTextureCS::ETextureType::Texture2DFloatNoMSAA;
}
}
SubresourceDumpDesc.ByteSize = SIZE_T(SubresourceDumpDesc.SubResourceExtent.X) * SIZE_T(SubresourceDumpDesc.SubResourceExtent.Y) * SIZE_T(GPixelFormats[SubresourceDumpDesc.PreprocessedPixelFormat].BlockBytes);
// Whether the subresource need preprocessing pass before copy into staging.
SubresourceDumpDesc.bPreprocessForStaging = SubresourceDumpDesc.PreprocessedPixelFormat != Desc.Format || SubresourceDesc.MipLevel != 0;
return SubresourceDumpDesc;
}
void DumpTextureSubResource(
FRHICommandListImmediate& RHICmdList,
const TCHAR* TextureDebugName,
FRHITexture* Texture,
FRHIShaderResourceView* SubResourceSRV,
const FTextureSubresourceDumpDesc& SubresourceDumpDesc,
const FString& DumpFilePath)
{
check(IsInRenderingThread());
// Preprocess
FTextureRHIRef StagingSrcTexture;
if (SubresourceDumpDesc.bPreprocessForStaging)
{
{
FRHIResourceCreateInfo CreateInfo(TEXT("DumpGPU.PreprocessTexture"));
StagingSrcTexture = RHICreateTexture2D(
SubresourceDumpDesc.SubResourceExtent.X,
SubresourceDumpDesc.SubResourceExtent.Y,
SubresourceDumpDesc.PreprocessedPixelFormat,
/* NumMips = */ 1,
/* NumSamples = */ 1,
TexCreate_UAV | TexCreate_ShaderResource | TexCreate_HideInVisualizeTexture,
CreateInfo);
}
FUnorderedAccessViewRHIRef StagingOutput = RHICreateUnorderedAccessView(StagingSrcTexture, /* MipLevel = */ 0);
RHICmdList.Transition(FRHITransitionInfo(StagingSrcTexture, ERHIAccess::Unknown, ERHIAccess::UAVCompute));
FDumpTextureCS::FPermutationDomain PermutationVector;
PermutationVector.Set<FDumpTextureCS::FTextureTypeDim>(SubresourceDumpDesc.DumpTextureType);
TShaderMapRef<FDumpTextureCS> ComputeShader(GetGlobalShaderMap(GMaxRHIShaderPlatform), PermutationVector);
FDumpTextureCS::FParameters ShaderParameters;
ShaderParameters.Texture = SubResourceSRV;
ShaderParameters.StagingOutput = StagingOutput;
FComputeShaderUtils::Dispatch(
RHICmdList,
ComputeShader,
ShaderParameters,
FComputeShaderUtils::GetGroupCount(SubresourceDumpDesc.SubResourceExtent, 8));
RHICmdList.Transition(FRHITransitionInfo(StagingSrcTexture, ERHIAccess::UAVCompute, ERHIAccess::CopySrc));
}
else
{
StagingSrcTexture = Texture;
}
// Copy the texture for CPU readback
FTextureRHIRef StagingTexture;
{
FRHIResourceCreateInfo CreateInfo(TEXT("DumpGPU.StagingTexture"));
StagingTexture = RHICreateTexture2D(
SubresourceDumpDesc.SubResourceExtent.X,
SubresourceDumpDesc.SubResourceExtent.Y,
SubresourceDumpDesc.PreprocessedPixelFormat,
/* NumMips = */ 1,
/* NumSamples = */ 1,
TexCreate_CPUReadback | TexCreate_HideInVisualizeTexture,
CreateInfo);
RHICmdList.Transition(FRHITransitionInfo(StagingTexture, ERHIAccess::Unknown, ERHIAccess::CopyDest));
// Ensure this copy call does not perform any transitions. We're handling them manually.
FResolveParams ResolveParams;
ResolveParams.SourceAccessFinal = ERHIAccess::Unknown;
ResolveParams.DestAccessFinal = ERHIAccess::Unknown;
// Transfer memory GPU -> CPU
RHICmdList.CopyToResolveTarget(StagingSrcTexture, StagingTexture, ResolveParams);
RHICmdList.Transition(FRHITransitionInfo(StagingTexture, ERHIAccess::CopyDest, ERHIAccess::CPURead));
}
// Submit to GPU and wait for completion.
static const FName FenceName(TEXT("DumpGPU.TextureFence"));
FGPUFenceRHIRef Fence = RHICreateGPUFence(FenceName);
{
Fence->Clear();
RHICmdList.WriteGPUFence(Fence);
RHICmdList.SubmitCommandsAndFlushGPU();
RHICmdList.BlockUntilGPUIdle();
}
void* Content = nullptr;
int32 RowPitchInPixels = 0;
int32 ColumnPitchInPixels = 0;
RHICmdList.MapStagingSurface(StagingTexture, Fence.GetReference(), Content, RowPitchInPixels, ColumnPitchInPixels);
if (Content)
{
TArray64<uint8> Array;
Array.SetNumUninitialized(SubresourceDumpDesc.ByteSize);
SIZE_T BytePerPixel = SIZE_T(GPixelFormats[SubresourceDumpDesc.PreprocessedPixelFormat].BlockBytes);
const uint8* SrcData = static_cast<const uint8*>(Content);
for (int32 y = 0; y < SubresourceDumpDesc.SubResourceExtent.Y; y++)
{
// Flip the data to be bottom left corner for the WebGL viewer.
const uint8* SrcPos = SrcData + SIZE_T(SubresourceDumpDesc.SubResourceExtent.Y - 1 - y) * SIZE_T(RowPitchInPixels) * BytePerPixel;
uint8* DstPos = (&Array[0]) + SIZE_T(y) * SIZE_T(SubresourceDumpDesc.SubResourceExtent.X) * BytePerPixel;
FPlatformMemory::Memmove(DstPos, SrcPos, SIZE_T(SubresourceDumpDesc.SubResourceExtent.X) * BytePerPixel);
}
RHICmdList.UnmapStagingSurface(StagingTexture);
DumpBinaryToFile(Array, DumpFilePath);
}
else
{
UE_LOG(LogRendererCore, Warning, TEXT("RHICmdList.MapStagingSurface() to dump texture %s failed."), TextureDebugName);
}
}
void DumpDrawTextureSubResource(
FRHICommandList& RHICmdList,
FRDGTextureSRVDesc SubresourceDesc,
ERHIAccess RHIAccessState)
{
check(IsInRenderingThread());
FRHICommandListImmediate& RHICmdListImmediate = FRHICommandListExecutor::GetImmediateCommandList();
check(&RHICmdListImmediate == &RHICmdList);
const FString UniqueResourceSubResourceName = GetUniqueSubResourceName(SubresourceDesc);
const FTextureSubresourceDumpDesc SubresourceDumpDesc = TranslateSubresourceDumpDesc(SubresourceDesc);
if (!SubresourceDumpDesc.IsDumpSupported())
{
return;
}
FRHITexture* RHITexture = SubresourceDesc.Texture->GetRHI();
FShaderResourceViewRHIRef SubResourceSRV;
if (SubresourceDumpDesc.bPreprocessForStaging)
{
SubResourceSRV = RHICreateShaderResourceView(RHITexture, FRHITextureSRVCreateInfo(SubresourceDesc));
RHICmdListImmediate.Transition(FRHITransitionInfo(RHITexture, ERHIAccess::Unknown, ERHIAccess::SRVCompute));
}
else
{
RHICmdListImmediate.Transition(FRHITransitionInfo(RHITexture, RHIAccessState, ERHIAccess::CopySrc));
}
FString DumpFilePath = kResourcesDir / FString::Printf(
TEXT("%s.v%016x.d%d.bin"),
*UniqueResourceSubResourceName,
PtrToUint(DrawDumpingPass),
DrawDumpCount);
DumpTextureSubResource(
RHICmdListImmediate,
SubresourceDesc.Texture->Name,
RHITexture,
SubResourceSRV,
SubresourceDumpDesc,
DumpFilePath);
if (SubresourceDumpDesc.bPreprocessForStaging)
{
RHICmdListImmediate.Transition(FRHITransitionInfo(RHITexture, ERHIAccess::SRVCompute, RHIAccessState));
}
else
{
RHICmdListImmediate.Transition(FRHITransitionInfo(RHITexture, ERHIAccess::CopySrc, RHIAccessState));
}
SubResourceSRV = nullptr;
ReleaseRHIResources(RHICmdListImmediate);
}
void AddDumpTextureSubResourcePass(
FRDGBuilder& GraphBuilder,
TArray<TSharedPtr<FJsonValue>>& InputResourceNames,
TArray<TSharedPtr<FJsonValue>>& OutputResourceNames,
const FRDGPass* Pass,
FRDGTextureSRVDesc SubresourceDesc,
bool bIsOutputResource)
{
int32 DumpTextureMode = GDumpTextureCVar.GetValueOnRenderThread();
if (DumpTextureMode == 0)
{
return;
}
const FRDGTextureDesc& Desc = SubresourceDesc.Texture->Desc;
const FString UniqueResourceName = GetUniqueResourceName(SubresourceDesc.Texture);
const FString UniqueResourceSubResourceName = GetUniqueSubResourceName(SubresourceDesc);
const FTextureSubresourceDumpDesc SubresourceDumpDesc = TranslateSubresourceDumpDesc(SubresourceDesc);
if (bIsOutputResource)
{
OutputResourceNames.AddUnique(MakeShareable(new FJsonValueString(UniqueResourceSubResourceName)));
}
else
{
InputResourceNames.AddUnique(MakeShareable(new FJsonValueString(UniqueResourceSubResourceName)));
}
bool bDumpResourceInfos;
bool bDumpResourceBinary;
GetResourceDumpInfo(Pass, SubresourceDesc.Texture, bIsOutputResource, &bDumpResourceInfos, &bDumpResourceBinary);
// Dump the information of the texture to json file.
if (bDumpResourceInfos)
{
TSharedPtr<FJsonObject> JsonObject = ToJson(SubresourceDesc.Texture->Name, Desc);
FString ResourceInfoJsonPath = kResourcesDir / UniqueResourceName + TEXT(".json");
DumpJsonToFile(JsonObject, ResourceInfoJsonPath);
}
if (!SubresourceDumpDesc.IsDumpSupported())
{
return;
}
// Early return if this resource shouldn't be dumped.
if (!bDumpResourceBinary || DumpTextureMode != 2)
{
return;
}
FString DumpFilePath = kResourcesDir / FString::Printf(TEXT("%s.v%016x.bin"), *UniqueResourceSubResourceName, PtrToUint(bIsOutputResource ? Pass : nullptr));
// Verify there is enough available memory to dump the resource.
if (IsUnsafeToDumpResource(SubresourceDumpDesc.ByteSize, 2.2f + (SubresourceDumpDesc.bPreprocessForStaging ? 1.0f : 0.0f)))
{
UE_LOG(LogRendererCore, Warning, TEXT("Not dumping %s because of insuficient memory available for staging texture."), *DumpFilePath);
return;
}
// Dump the resource's binary to a .bin file.
{
FDumpTexturePass* PassParameters = GraphBuilder.AllocParameters<FDumpTexturePass>();
if (SubresourceDumpDesc.bPreprocessForStaging)
{
if (!(SubresourceDesc.Texture->Desc.Flags & TexCreate_ShaderResource))
{
UE_LOG(LogRendererCore, Warning, TEXT("Not dumping %s because requires copy to staging texture using compute, but is missing TexCreate_ShaderResource."), *UniqueResourceSubResourceName);
return;
}
if (!FDumpTextureCS::IsSupported(GMaxRHIShaderPlatform))
{
UE_LOG(LogRendererCore, Warning, TEXT("Not dumping %s because FDumpTextureCS compute shader is not supported."), *UniqueResourceSubResourceName);
return;
}
PassParameters->Texture = GraphBuilder.CreateSRV(SubresourceDesc);
}
else
{
PassParameters->TextureAccess = FRDGTextureAccess(SubresourceDesc.Texture, ERHIAccess::CopySrc);
}
GraphBuilder.AddPass(
RDG_EVENT_NAME("RDG DumpTexture(%s -> %s) %dx%d",
SubresourceDesc.Texture->Name, *DumpFilePath,
SubresourceDumpDesc.SubResourceExtent.X, SubresourceDumpDesc.SubResourceExtent.Y),
PassParameters,
(SubresourceDumpDesc.bPreprocessForStaging ? ERDGPassFlags::Compute : ERDGPassFlags::Copy) | ERDGPassFlags::NeverCull,
[
PassParameters, this, DumpFilePath,
SubresourceDesc, SubresourceDumpDesc]
(FRHICommandListImmediate& RHICmdList)
{
this->DumpTextureSubResource(
RHICmdList,
SubresourceDesc.Texture->Name,
SubresourceDumpDesc.bPreprocessForStaging ? nullptr : SubresourceDesc.Texture->GetRHI(),
SubresourceDumpDesc.bPreprocessForStaging ? PassParameters->Texture->GetRHI() : nullptr,
SubresourceDumpDesc,
DumpFilePath);
this->ReleaseRHIResources(RHICmdList);
this->UpdatePassProgress();
});
ResourcesDumpPasses++;
}
}
void AddDumpTexturePasses(
FRDGBuilder& GraphBuilder,
TArray<TSharedPtr<FJsonValue>>& InputResourceNames,
TArray<TSharedPtr<FJsonValue>>& OutputResourceNames,
const FRDGPass* Pass,
FRDGTextureSRVDesc SubresourceRangeDesc,
bool bIsOutputResource)
{
if (SubresourceRangeDesc.Format == PF_X24_G8)
{
AddDumpTextureSubResourcePass(
GraphBuilder,
InputResourceNames,
OutputResourceNames,
Pass,
SubresourceRangeDesc,
bIsOutputResource);
}
else
{
for (int32 MipLevel = SubresourceRangeDesc.MipLevel; MipLevel < (SubresourceRangeDesc.MipLevel + SubresourceRangeDesc.NumMipLevels); MipLevel++)
{
FRDGTextureSRVDesc SubresourceDesc = FRDGTextureSRVDesc::CreateForMipLevel(SubresourceRangeDesc.Texture, MipLevel);
AddDumpTextureSubResourcePass(
GraphBuilder,
InputResourceNames,
OutputResourceNames,
Pass,
SubresourceDesc,
bIsOutputResource);
}
}
}
void AddDumpBufferPass(
FRDGBuilder& GraphBuilder,
TArray<TSharedPtr<FJsonValue>>& InputResourceNames,
TArray<TSharedPtr<FJsonValue>>& OutputResourceNames,
const FRDGPass* Pass,
FRDGBuffer* Buffer,
bool bIsOutputResource)
{
int32 DumpBufferMode = GDumpTextureCVar.GetValueOnRenderThread();
if (DumpBufferMode == 0)
{
return;
}
FString UniqueResourceName = GetUniqueResourceName(Buffer);
if (bIsOutputResource)
{
OutputResourceNames.AddUnique(MakeShareable(new FJsonValueString(UniqueResourceName)));
}
else
{
InputResourceNames.AddUnique(MakeShareable(new FJsonValueString(UniqueResourceName)));
}
const FRDGBufferDesc& Desc = Buffer->Desc;
const int32 ByteSize = Desc.GetTotalNumBytes();
bool bDumpResourceInfos;
bool bDumpResourceBinary;
GetResourceDumpInfo(Pass, Buffer, bIsOutputResource, &bDumpResourceInfos, &bDumpResourceBinary);
// Dump the information of the buffer to json file.
if (bDumpResourceInfos)
{
TSharedPtr<FJsonObject> JsonObject = ToJson(Buffer->Name, Desc, ByteSize);
FString ResourceInfoJsonPath = kResourcesDir / UniqueResourceName + TEXT(".json");
DumpJsonToFile(JsonObject, ResourceInfoJsonPath);
if (Desc.Metadata && DumpBufferMode == 2)
{
Dump(Desc.Metadata);
}
}
if (Desc.UnderlyingType == FRDGBufferDesc::EUnderlyingType::AccelerationStructure)
{
return;
}
// Dump the resource's binary to a .bin file.
if (bDumpResourceBinary && DumpBufferMode == 2)
{
FString DumpFilePath = kResourcesDir / FString::Printf(TEXT("%s.v%016x.bin"), *UniqueResourceName, PtrToUint(bIsOutputResource ? Pass : nullptr));
if (IsUnsafeToDumpResource(ByteSize, 1.2f))
{
UE_LOG(LogRendererCore, Warning, TEXT("Not dumping %s because of insuficient memory available for staging buffer."), *DumpFilePath);
return;
}
FDumpBufferPass* PassParameters = GraphBuilder.AllocParameters<FDumpBufferPass>();
PassParameters->Buffer = Buffer;
GraphBuilder.AddPass(
RDG_EVENT_NAME("RDG DumpBuffer(%s -> %s)", Buffer->Name, *DumpFilePath),
PassParameters,
ERDGPassFlags::Readback,
[this, DumpFilePath, Buffer, ByteSize](FRHICommandListImmediate& RHICmdList)
{
check(IsInRenderingThread());
FStagingBufferRHIRef StagingBuffer = RHICreateStagingBuffer();
// Transfer memory GPU -> CPU
RHICmdList.CopyToStagingBuffer(Buffer->GetRHI(), StagingBuffer, 0, ByteSize);
// Submit to GPU and wait for completion.
static const FName FenceName(TEXT("DumpGPU.BufferFence"));
FGPUFenceRHIRef Fence = RHICreateGPUFence(FenceName);
{
Fence->Clear();
RHICmdList.WriteGPUFence(Fence);
RHICmdList.SubmitCommandsAndFlushGPU();
RHICmdList.BlockUntilGPUIdle();
}
void* Content = RHICmdList.LockStagingBuffer(StagingBuffer, Fence.GetReference(), 0, ByteSize);
if (Content)
{
TArrayView<const uint8> ArrayView(reinterpret_cast<const uint8*>(Content), ByteSize);
DumpBinaryToFile(ArrayView, DumpFilePath);
RHICmdList.UnlockStagingBuffer(StagingBuffer);
}
else
{
UE_LOG(LogRendererCore, Warning, TEXT("RHICmdList.LockStagingBuffer() to dump buffer %s failed."), Buffer->Name);
}
StagingBuffer = nullptr;
Fence = nullptr;
this->ReleaseRHIResources(RHICmdList);
this->UpdatePassProgress();
});
ResourcesDumpPasses++;
}
}
// Look whether the pass matches matches r.DumpGPU.Root
bool IsDumpingPass(const FRDGPass* Pass)
{
FString RootWildcardString = GDumpGPURootCVar.GetValueOnRenderThread();
FWildcardString WildcardFilter(RootWildcardString);
bool bDumpPass = (RootWildcardString == TEXT("*"));
if (!bDumpPass)
{
bDumpPass = WildcardFilter.IsMatch(Pass->GetEventName().GetTCHAR());
}
#if RDG_GPU_SCOPES
if (!bDumpPass)
{
const FRDGEventScope* ParentScope = Pass->GetGPUScopes().Event;
while (ParentScope)
{
bDumpPass = bDumpPass || WildcardFilter.IsMatch(ParentScope->Name.GetTCHAR());
ParentScope = ParentScope->ParentScope;
}
}
#endif
return bDumpPass;
}
};
bool bIsDumpingFrame_GameThread = false;
FRDGResourceDumpContext GRDGResourceDumpContext;
}
bool IsDumpingRDGResources()
{
return GRDGResourceDumpContext.IsDumpingFrame();
}
FString FRDGBuilder::BeginResourceDump(const TArray<FString>& Args)
{
check(IsInGameThread());
if (bIsDumpingFrame_GameThread)
{
return FString();
}
FRDGResourceDumpContext NewResourceDumpContext;
NewResourceDumpContext.Time = FDateTime::Now();
{
FString MasterDirectoryPath = GDumpGPUDirectoryCVar.GetValueOnGameThread();
if (MasterDirectoryPath.IsEmpty())
{
MasterDirectoryPath = FPaths::ProjectSavedDir() / TEXT("GPUDumps/");
}
NewResourceDumpContext.DumpingDirectoryPath = MasterDirectoryPath / FApp::GetProjectName() + TEXT("-") + FPlatformProperties::PlatformName() + TEXT("-") + NewResourceDumpContext.Time.ToString() + TEXT("/");
}
NewResourceDumpContext.bEnableDiskWrite = GDumpTestEnableDiskWrite.GetValueOnGameThread() != 0;
NewResourceDumpContext.bShowInExplore = NewResourceDumpContext.bEnableDiskWrite && GDumpExploreCVar.GetValueOnGameThread() != 0;
NewResourceDumpContext.MemoryConstants = FPlatformMemory::GetConstants();
NewResourceDumpContext.MemoryStats = FPlatformMemory::GetStats();
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if (NewResourceDumpContext.bEnableDiskWrite)
{
if (!PlatformFile.DirectoryExists(*NewResourceDumpContext.DumpingDirectoryPath))
{
PlatformFile.CreateDirectoryTree(*NewResourceDumpContext.DumpingDirectoryPath);
}
PlatformFile.CreateDirectoryTree(*(NewResourceDumpContext.DumpingDirectoryPath / FRDGResourceDumpContext::kBaseDir));
PlatformFile.CreateDirectoryTree(*(NewResourceDumpContext.DumpingDirectoryPath / FRDGResourceDumpContext::kResourcesDir));
NewResourceDumpContext.DumpStringToFile(TEXT(""), FString(FRDGResourceDumpContext::kBaseDir) / TEXT("PassDrawCounts.json"));
}
// Output informations
{
const TCHAR* BranchName = BuildSettings::GetBranchName();
const TCHAR* BuildDate = BuildSettings::GetBuildDate();
const TCHAR* BuildVersion = BuildSettings::GetBuildVersion();
FString BuildConfiguration = LexToString(FApp::GetBuildConfiguration());
FString BuildTarget = LexToString(FApp::GetBuildTargetType());
FGPUDriverInfo GPUDriverInfo = FPlatformMisc::GetGPUDriverInfo(GRHIAdapterName);
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("Project"), FApp::GetProjectName());
JsonObject->SetNumberField(TEXT("EngineMajorVersion"), ENGINE_MAJOR_VERSION);
JsonObject->SetNumberField(TEXT("EngineMinorVersion"), ENGINE_MINOR_VERSION);
JsonObject->SetNumberField(TEXT("EnginePatchVersion"), ENGINE_PATCH_VERSION);
JsonObject->SetStringField(TEXT("BuildBranch"), BranchName ? BranchName : TEXT(""));
JsonObject->SetStringField(TEXT("BuildDate"), BuildDate ? BuildDate : TEXT(""));
JsonObject->SetStringField(TEXT("BuildVersion"), BuildVersion ? BuildVersion : TEXT(""));
JsonObject->SetStringField(TEXT("BuildTarget"), BuildTarget);
JsonObject->SetStringField(TEXT("BuildConfiguration"), BuildConfiguration);
JsonObject->SetNumberField(TEXT("Build64Bits"), (PLATFORM_64BITS ? 1 : 0));
JsonObject->SetStringField(TEXT("Platform"), FPlatformProperties::IniPlatformName());
JsonObject->SetStringField(TEXT("DeviceName"), FPlatformProcess::ComputerName());
JsonObject->SetStringField(TEXT("CPUVendor"), FPlatformMisc::GetCPUVendor());
JsonObject->SetStringField(TEXT("CPUBrand"), FPlatformMisc::GetCPUBrand());
JsonObject->SetNumberField(TEXT("CPUNumberOfCores"), FPlatformMisc::NumberOfCores());
JsonObject->SetNumberField(TEXT("CPUNumberOfCoresIncludingHyperthreads"), FPlatformMisc::NumberOfCoresIncludingHyperthreads());
JsonObject->SetStringField(TEXT("GPUVendor"), RHIVendorIdToString());
JsonObject->SetStringField(TEXT("GPUDeviceDescription"), GPUDriverInfo.DeviceDescription);
JsonObject->SetStringField(TEXT("GPUDriverUserVersion"), GPUDriverInfo.UserDriverVersion);
JsonObject->SetStringField(TEXT("GPUDriverInternalVersion"), GPUDriverInfo.GetUnifiedDriverVersion());
JsonObject->SetStringField(TEXT("GPUDriverDate"), GPUDriverInfo.DriverDate);
JsonObject->SetNumberField(TEXT("MemoryTotalPhysical"), NewResourceDumpContext.MemoryConstants.TotalPhysical);
JsonObject->SetNumberField(TEXT("MemoryPageSize"), NewResourceDumpContext.MemoryConstants.PageSize);
JsonObject->SetStringField(TEXT("RHI"), GDynamicRHI->GetName());
JsonObject->SetStringField(TEXT("RHIMaxFeatureLevel"), LexToString(GMaxRHIFeatureLevel));
JsonObject->SetStringField(TEXT("DumpTime"), NewResourceDumpContext.Time.ToString());
NewResourceDumpContext.DumpJsonToFile(JsonObject, FString(FRDGResourceDumpContext::kBaseDir) / TEXT("Infos.json"));
}
// Dump the rendering cvars
if (NewResourceDumpContext.bEnableDiskWrite)
{
NewResourceDumpContext.DumpRenderingCVarsToCSV();
}
// Copy the viewer
if (NewResourceDumpContext.bEnableDiskWrite)
{
const TCHAR* OpenGPUDumpViewerWindowsName = TEXT("OpenGPUDumpViewer.bat");
const TCHAR* ViewerHTML = TEXT("GPUDumpViewer.html");
FString DumpGPUViewerSourcePath = FPaths::EngineDir() + FString(TEXT("Extras")) / TEXT("GPUDumpViewer");
PlatformFile.CopyFile(*(NewResourceDumpContext.DumpingDirectoryPath / ViewerHTML), *(DumpGPUViewerSourcePath / ViewerHTML));
PlatformFile.CopyFile(*(NewResourceDumpContext.DumpingDirectoryPath / OpenGPUDumpViewerWindowsName), *(DumpGPUViewerSourcePath / OpenGPUDumpViewerWindowsName));
}
ENQUEUE_RENDER_COMMAND(FStartGPUDump)(
[NewResourceDumpContext](FRHICommandListImmediate& ImmediateRHICmdList)
{
check(IsInRenderingThread());
GRDGResourceDumpContext = NewResourceDumpContext;
ImmediateRHICmdList.SubmitCommandsAndFlushGPU();
// Disable the validation for BUF_SourceCopy so that all buffers can be copied into staging buffer for CPU readback.
#if ENABLE_RHI_VALIDATION
GRHIValidateBufferSourceCopy = false;
#endif
});
bIsDumpingFrame_GameThread = true;
if (NewResourceDumpContext.bEnableDiskWrite)
{
return NewResourceDumpContext.DumpingDirectoryPath;
}
return FString();
}
void FRDGBuilder::EndResourceDump()
{
check(IsInGameThread());
if (!bIsDumpingFrame_GameThread)
{
return;
}
// Wait all rendering commands are completed to finish with GRDGResourceDumpContext.
{
UE_LOG(LogRendererCore, Display, TEXT("Stalling game thread until render thread finishes to dump resources"));
ENQUEUE_RENDER_COMMAND(FEndGPUDump)(
[](FRHICommandListImmediate& ImmediateRHICmdList)
{
ImmediateRHICmdList.SubmitCommandsAndFlushGPU();
#if ENABLE_RHI_VALIDATION
GRHIValidateBufferSourceCopy = true;
#endif
});
FlushRenderingCommands();
}
// Log information about the dump.
FString AbsDumpingDirectoryPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*GRDGResourceDumpContext.DumpingDirectoryPath);
{
UE_LOG(LogRendererCore, Display, TEXT("Dumped %d resources to %s"), GRDGResourceDumpContext.ResourcesDumpPasses, *AbsDumpingDirectoryPath);
}
// Dump the log into the dump directory.
if (GRDGResourceDumpContext.bEnableDiskWrite)
{
if (GLog)
{
GLog->FlushThreadedLogs();
GLog->Flush();
}
FGenericCrashContext::DumpLog(GRDGResourceDumpContext.DumpingDirectoryPath / FRDGResourceDumpContext::kBaseDir);
}
#if PLATFORM_DESKTOP
if (GRDGResourceDumpContext.bShowInExplore)
{
FPlatformProcess::ExploreFolder(*AbsDumpingDirectoryPath);
}
#endif
GRDGResourceDumpContext = FRDGResourceDumpContext();
bIsDumpingFrame_GameThread = false;
}
void FRDGBuilder::DumpResourcePassOutputs(const FRDGPass* Pass)
{
if (bInDebugPassScope)
{
return;
}
if (!GRDGResourceDumpContext.IsDumpingFrame())
{
return;
}
check(IsInRenderingThread());
if (!GRDGResourceDumpContext.IsDumpingPass(Pass))
{
return;
}
bInDebugPassScope = true;
TArray<TSharedPtr<FJsonValue>> InputResourceNames;
TArray<TSharedPtr<FJsonValue>> OutputResourceNames;
Pass->GetParameters().Enumerate([&](FRDGParameter Parameter)
{
switch (Parameter.GetType())
{
case UBMT_RDG_TEXTURE:
{
if (FRDGTextureRef Texture = Parameter.GetAsTexture())
{
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::Create(Texture);
GRDGResourceDumpContext.AddDumpTexturePasses(*this, InputResourceNames, OutputResourceNames, Pass, TextureSubResource, /* bIsOutputResource = */ false);
}
}
break;
case UBMT_RDG_TEXTURE_SRV:
{
if (FRDGTextureSRVRef SRV = Parameter.GetAsTextureSRV())
{
if (SRV->Desc.MetaData == ERHITextureMetaDataAccess::None)
{
GRDGResourceDumpContext.AddDumpTexturePasses(*this, InputResourceNames, OutputResourceNames, Pass, SRV->Desc, /* bIsOutputResource = */ false);
}
else
{
UE_LOG(LogRendererCore, Warning, TEXT("Dumping texture %s's meta data unsupported"), SRV->Desc.Texture->Name);
}
}
}
break;
case UBMT_RDG_TEXTURE_UAV:
{
if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV())
{
if (UAV->Desc.MetaData == ERHITextureMetaDataAccess::None)
{
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::CreateForMipLevel(UAV->Desc.Texture, UAV->Desc.MipLevel);
GRDGResourceDumpContext.AddDumpTextureSubResourcePass(*this, InputResourceNames, OutputResourceNames, Pass, TextureSubResource, /* bIsOutputResource = */ true);
}
else
{
UE_LOG(LogRendererCore, Warning, TEXT("Dumping texture %s's meta data unsupported"), UAV->Desc.Texture->Name);
}
}
}
break;
case UBMT_RDG_TEXTURE_ACCESS:
{
if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess())
{
bool bIsOutputResource = (
TextureAccess.GetAccess() == ERHIAccess::UAVCompute ||
TextureAccess.GetAccess() == ERHIAccess::UAVGraphics ||
TextureAccess.GetAccess() == ERHIAccess::RTV);
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::Create(TextureAccess);
GRDGResourceDumpContext.AddDumpTexturePasses(*this, InputResourceNames, OutputResourceNames, Pass, TextureSubResource, bIsOutputResource);
}
}
break;
case UBMT_RDG_TEXTURE_ACCESS_ARRAY:
{
const FRDGTextureAccessArray& TextureAccessArray = Parameter.GetAsTextureAccessArray();
for (FRDGTextureAccess TextureAccess : TextureAccessArray)
{
bool bIsOutputResource = (
TextureAccess.GetAccess() == ERHIAccess::UAVCompute ||
TextureAccess.GetAccess() == ERHIAccess::UAVGraphics ||
TextureAccess.GetAccess() == ERHIAccess::RTV);
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::Create(TextureAccess);
GRDGResourceDumpContext.AddDumpTexturePasses(*this, InputResourceNames, OutputResourceNames, Pass, TextureSubResource, bIsOutputResource);
}
}
break;
case UBMT_RDG_BUFFER_SRV:
{
if (FRDGBufferSRVRef SRV = Parameter.GetAsBufferSRV())
{
FRDGBufferRef Buffer = SRV->Desc.Buffer;
GRDGResourceDumpContext.AddDumpBufferPass(*this, InputResourceNames, OutputResourceNames, Pass, Buffer, /* bIsOutputResource = */ false);
}
}
break;
case UBMT_RDG_BUFFER_UAV:
{
if (FRDGBufferUAVRef UAV = Parameter.GetAsBufferUAV())
{
FRDGBufferRef Buffer = UAV->Desc.Buffer;
GRDGResourceDumpContext.AddDumpBufferPass(*this, InputResourceNames, OutputResourceNames, Pass, Buffer, /* bIsOutputResource = */ true);
}
}
break;
case UBMT_RDG_BUFFER_ACCESS:
{
if (FRDGBufferAccess BufferAccess = Parameter.GetAsBufferAccess())
{
bool bIsOutputResource = (
BufferAccess.GetAccess() == ERHIAccess::UAVCompute ||
BufferAccess.GetAccess() == ERHIAccess::UAVGraphics);
GRDGResourceDumpContext.AddDumpBufferPass(*this, InputResourceNames, OutputResourceNames, Pass, BufferAccess, bIsOutputResource);
}
}
break;
case UBMT_RDG_BUFFER_ACCESS_ARRAY:
{
const FRDGBufferAccessArray& BufferAccessArray = Parameter.GetAsBufferAccessArray();
for (FRDGBufferAccess BufferAccess : BufferAccessArray)
{
bool bIsOutputResource = (
BufferAccess.GetAccess() == ERHIAccess::UAVCompute ||
BufferAccess.GetAccess() == ERHIAccess::UAVGraphics);
GRDGResourceDumpContext.AddDumpBufferPass(*this, InputResourceNames, OutputResourceNames, Pass, BufferAccess, bIsOutputResource);
}
}
break;
case UBMT_RENDER_TARGET_BINDING_SLOTS:
{
const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots();
RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget)
{
FRDGTextureRef Texture = RenderTarget.GetTexture();
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::CreateForMipLevel(Texture, RenderTarget.GetMipIndex());
GRDGResourceDumpContext.AddDumpTextureSubResourcePass(*this, InputResourceNames, OutputResourceNames, Pass, TextureSubResource, /* bIsOutputResource = */ true);
});
const FDepthStencilBinding& DepthStencil = RenderTargets.DepthStencil;
if (FRDGTextureRef Texture = DepthStencil.GetTexture())
{
FExclusiveDepthStencil DepthStencilAccess = DepthStencil.GetDepthStencilAccess();
if (DepthStencilAccess.IsUsingDepth())
{
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::CreateForMipLevel(Texture, 0);
GRDGResourceDumpContext.AddDumpTextureSubResourcePass(
*this, InputResourceNames, OutputResourceNames, Pass, TextureSubResource,
/* bIsOutputResource = */ DepthStencilAccess.IsDepthWrite());
}
if (DepthStencilAccess.IsUsingStencil())
{
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::CreateWithPixelFormat(Texture, PF_X24_G8);
GRDGResourceDumpContext.AddDumpTextureSubResourcePass(
*this, InputResourceNames, OutputResourceNames, Pass, TextureSubResource,
/* bIsOutputResource = */ DepthStencilAccess.IsStencilWrite());
}
}
}
break;
}
});
// Dump the pass informations
{
TArray<TSharedPtr<FJsonValue>> ParentEventScopeNames;
#if RDG_GPU_SCOPES
{
const FRDGEventScope* ParentScope = Pass->GetGPUScopes().Event;
while (ParentScope)
{
ParentEventScopeNames.Add(MakeShareable(new FJsonValueString(ParentScope->Name.GetTCHAR())));
ParentScope = ParentScope->ParentScope;
}
}
#endif
{
ParentEventScopeNames.Add(MakeShareable(new FJsonValueString(FString::Printf(TEXT("Frame %llu"), GFrameCounterRenderThread))));
}
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("EventName"), Pass->GetEventName().GetTCHAR());
JsonObject->SetStringField(TEXT("ParametersName"), Pass->GetParameters().GetLayout().GetDebugName());
JsonObject->SetStringField(TEXT("Parameters"), FRDGResourceDumpContext::PtrToString(Pass->GetParameters().GetContents()));
JsonObject->SetStringField(TEXT("ParametersMetadata"), FRDGResourceDumpContext::PtrToString(Pass->GetParameters().GetMetadata()));
JsonObject->SetStringField(TEXT("Pointer"), FString::Printf(TEXT("%016x"), FRDGResourceDumpContext::PtrToUint(Pass)));
JsonObject->SetNumberField(TEXT("Id"), GRDGResourceDumpContext.PassesCount);
JsonObject->SetArrayField(TEXT("ParentEventScopes"), ParentEventScopeNames);
JsonObject->SetArrayField(TEXT("InputResources"), InputResourceNames);
JsonObject->SetArrayField(TEXT("OutputResources"), OutputResourceNames);
GRDGResourceDumpContext.DumpJsonToFile(JsonObject, FString(FRDGResourceDumpContext::kBaseDir) / TEXT("Passes.json"), FILEWRITE_Append);
}
// Dump the pass' parameters
if (GDumpGPUPassParameters.GetValueOnRenderThread() != 0)
{
int32 PassParametersByteSize = 0;
{
const FShaderParametersMetadata* Metadata = Pass->GetParameters().GetMetadata();
if (Metadata)
{
GRDGResourceDumpContext.Dump(Metadata);
PassParametersByteSize = Metadata->GetSize();
}
}
if (PassParametersByteSize == 0 && Pass->GetParameters().GetLayoutPtr())
{
PassParametersByteSize = Pass->GetParameters().GetLayout().ConstantBufferSize;
}
const uint8* PassParametersContent = Pass->GetParameters().GetContents();
if (PassParametersContent && !GRDGResourceDumpContext.IsDumped(PassParametersContent))
{
TArrayView<const uint8> ArrayView(PassParametersContent, PassParametersByteSize);
FString DumpFilePath = FRDGResourceDumpContext::kStructuresDir / FRDGResourceDumpContext::PtrToString(PassParametersContent) + TEXT(".bin");
GRDGResourceDumpContext.DumpBinaryToFile(ArrayView, DumpFilePath);
GRDGResourceDumpContext.SetDumped(PassParametersContent);
}
}
GRDGResourceDumpContext.PassesCount++;
bInDebugPassScope = false;
}
#if RDG_DUMP_RESOURCES_AT_EACH_DRAW
void FRDGBuilder::BeginPassDump(const FRDGPass* Pass)
{
if (!GRDGResourceDumpContext.IsDumpingFrame())
{
return;
}
if (!GDumpGPUDraws.GetValueOnRenderThread())
{
return;
}
if (!EnumHasAnyFlags(Pass->GetFlags(), ERDGPassFlags::Raster))
{
return;
}
if (!IsInRenderingThread())
{
UE_LOG(LogRendererCore, Warning, TEXT("Couldn't start dumping draw's resources for pass %s because not in the rendering thread"), Pass->GetEventName().GetTCHAR());
return;
}
check(GRDGResourceDumpContext.DrawDumpingPass == nullptr);
if (GRDGResourceDumpContext.IsDumpingPass(Pass))
{
GRDGResourceDumpContext.DrawDumpingPass = Pass;
GRDGResourceDumpContext.DrawDumpCount = 0;
}
}
// static
void FRDGBuilder::DumpDraw(const FRDGEventName& DrawEventName)
{
if (!GRDGResourceDumpContext.IsDumpingFrame())
{
return;
}
if (!IsInRenderingThread())
{
UE_LOG(LogRendererCore, Warning, TEXT("Couldn't dump draw because not in the rendering thread"));
return;
}
if (!GRDGResourceDumpContext.DrawDumpingPass)
{
return;
}
const FRDGPass* Pass = GRDGResourceDumpContext.DrawDumpingPass;
FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();
if (EnumHasAnyFlags(Pass->GetFlags(), ERDGPassFlags::Raster))
{
RHICmdList.EndRenderPass();
}
Pass->GetParameters().Enumerate([&](FRDGParameter Parameter)
{
switch (Parameter.GetType())
{
case UBMT_RENDER_TARGET_BINDING_SLOTS:
{
const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots();
RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget)
{
FRDGTextureRef Texture = RenderTarget.GetTexture();
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::CreateForMipLevel(Texture, RenderTarget.GetMipIndex());
GRDGResourceDumpContext.DumpDrawTextureSubResource(
RHICmdList,
TextureSubResource,
ERHIAccess::RTV);
});
const FDepthStencilBinding& DepthStencil = RenderTargets.DepthStencil;
if (FRDGTextureRef Texture = DepthStencil.GetTexture())
{
FExclusiveDepthStencil DepthStencilAccess = DepthStencil.GetDepthStencilAccess();
if (DepthStencilAccess.IsDepthWrite())
{
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::CreateForMipLevel(Texture, 0);
GRDGResourceDumpContext.DumpDrawTextureSubResource(
RHICmdList,
TextureSubResource,
ERHIAccess::RTV);
}
if (DepthStencilAccess.IsStencilWrite())
{
FRDGTextureSRVDesc TextureSubResource = FRDGTextureSRVDesc::CreateWithPixelFormat(Texture, PF_X24_G8);
GRDGResourceDumpContext.DumpDrawTextureSubResource(
RHICmdList,
TextureSubResource,
ERHIAccess::RTV);
}
}
}
break;
}
});
if (EnumHasAnyFlags(Pass->GetFlags(), ERDGPassFlags::Raster))
{
RHICmdList.BeginRenderPass(Pass->GetParameters().GetRenderPassInfo(), Pass->GetName());
}
// Dump the draw even name
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("DrawName"), DrawEventName.GetTCHAR());
FString DumpFilePath = FRDGResourceDumpContext::kPassesDir / FString::Printf(TEXT("Pass.%016x.Draws.json"), FRDGResourceDumpContext::PtrToUint(Pass));
GRDGResourceDumpContext.DumpJsonToFile(JsonObject, DumpFilePath, FILEWRITE_Append);
}
GRDGResourceDumpContext.DrawDumpCount++;
if (GRDGResourceDumpContext.DrawDumpCount % 10 == 0)
{
UE_LOG(LogRendererCore, Display, TEXT("Dumped %d draws' resources"), GRDGResourceDumpContext.DrawDumpCount);
return;
}
}
void FRDGBuilder::EndPassDump(const FRDGPass* Pass)
{
if (!GRDGResourceDumpContext.IsDumpingFrame())
{
return;
}
if (!IsInRenderingThread())
{
return;
}
if (!GRDGResourceDumpContext.DrawDumpingPass)
{
return;
}
check(Pass == GRDGResourceDumpContext.DrawDumpingPass);
// Output how many draw has been dump for this pass.
if (GRDGResourceDumpContext.DrawDumpCount > 0)
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField(TEXT("EventName"), Pass->GetEventName().GetTCHAR());
JsonObject->SetStringField(TEXT("Pointer"), FString::Printf(TEXT("%016x"), FRDGResourceDumpContext::PtrToUint(Pass)));
JsonObject->SetNumberField(TEXT("DrawCount"), GRDGResourceDumpContext.DrawDumpCount);
GRDGResourceDumpContext.DumpJsonToFile(JsonObject, FString(FRDGResourceDumpContext::kBaseDir) / TEXT("PassDrawCounts.json"), FILEWRITE_Append);
UE_LOG(LogRendererCore, Display, TEXT("Completed dump of %d draws for pass: %s"), GRDGResourceDumpContext.DrawDumpCount, Pass->GetEventName().GetTCHAR());
}
GRDGResourceDumpContext.DrawDumpingPass = nullptr;
GRDGResourceDumpContext.DrawDumpCount = 0;
}
// static
bool FRDGBuilder::IsDumpingDraws()
{
if (!GRDGResourceDumpContext.IsDumpingFrame())
{
return false;
}
return GDumpGPUDraws.GetValueOnRenderThread() != 0;
}
#endif // RDG_DUMP_RESOURCES_AT_EACH_DRAW
#else //! RDG_DUMP_RESOURCES
bool IsDumpingRDGResources()
{
return false;
}
#endif //! RDG_DUMP_RESOURCES