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

2317 lines
80 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
RHI.cpp: Render Hardware Interface implementation.
=============================================================================*/
#include "RHI.h"
#include "Async/ParallelFor.h"
#include "HAL/IConsoleManager.h"
#include "HAL/LowLevelMemTracker.h"
#include "RHITransientResourceAllocator.h"
#include "Misc/CommandLine.h"
#include "Modules/ModuleManager.h"
#include "Misc/ConfigCacheIni.h"
#include "ProfilingDebugging/CsvProfiler.h"
#include "ProfilingDebugging/MetadataTrace.h"
#include "RHIFwd.h"
#include "String/LexFromString.h"
#include "RHIStrings.h"
#include "String/ParseTokens.h"
#include "Misc/BufferedOutputDevice.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Serialization/MemoryImage.h"
#include "Stats/StatsTrace.h"
#include "RHITextureReference.h"
#include "RHIStats.h"
#include "RHICommandList.h"
#include "RHIUniformBufferLayoutInitializer.h"
#include <type_traits>
#if RHI_ENABLE_RESOURCE_INFO
#include "HAL/FileManager.h"
#endif
IMPLEMENT_MODULE(FDefaultModuleImpl, RHI);
/** RHI Logging. */
DEFINE_LOG_CATEGORY(LogRHI);
CSV_DEFINE_CATEGORY(RHI, true);
#if UE_BUILD_SHIPPING
CSV_DEFINE_CATEGORY(DrawCall, false);
#else
CSV_DEFINE_CATEGORY(DrawCall, true);
#endif
IMPLEMENT_TYPE_LAYOUT(FRHIUniformBufferLayoutInitializer);
IMPLEMENT_TYPE_LAYOUT(FRHIUniformBufferResourceInitializer);
#if !defined(RHIRESOURCE_NUM_FRAMES_TO_EXPIRE)
#define RHIRESOURCE_NUM_FRAMES_TO_EXPIRE 3
#endif
static TAutoConsoleVariable<int32> CVarDisableEngineAndAppRegistration(
TEXT("r.DisableEngineAndAppRegistration"),
0,
TEXT("If true, disables engine and app registration, to disable GPU driver optimizations during debugging and development\n")
TEXT("Changes will only take effect in new game/editor instances - can't be changed at runtime.\n"),
ECVF_Default);
static TAutoConsoleVariable<int32> CVarGraphicsAdapter(
TEXT("r.GraphicsAdapter"),
-1,
TEXT("User request to pick a specific graphics adapter (e.g. when using a integrated graphics card with a discrete one)\n")
TEXT("For Windows D3D, unless a specific adapter is chosen we reject Microsoft adapters because we don't want the software emulation.\n")
TEXT("This takes precedence over -prefer{AMD|NVidia|Intel} when the value is >= 0.\n")
TEXT(" -2: Take the first one that fulfills the criteria\n")
TEXT(" -1: Favour non integrated because there are usually faster (default)\n")
TEXT(" 0: Adapter #0\n")
TEXT(" 1: Adapter #1, ..."),
ECVF_ReadOnly | ECVF_RenderThreadSafe);
static FAutoConsoleCommandWithWorldAndArgs CVarRHISetGPUCaptureOptions(
TEXT("r.RHISetGPUCaptureOptions"),
TEXT("Utility function to change multiple CVARs useful when profiling or debugging GPU rendering. Setting to 1 or 0 will guarantee all options are in the appropriate state.\n")
TEXT("r.showmaterialdrawevents, toggledrawevents."),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic([](const TArray<FString>& Args, UWorld* World)
{
if (Args.Num() > 0)
{
const bool bEnabled = Args[0].ToBool();
FDynamicRHI::EnableIdealGPUCaptureOptions(bEnabled);
}
else
{
UE_LOG(LogRHI, Display, TEXT("Usage: r.RHISetGPUCaptureOptions 0 or r.RHISetGPUCaptureOptions 1"));
}
})
);
#if STATS
#include "ProfilingDebugging/CsvProfilerConfig.h"
#include "Stats/StatsData.h"
static void DumpRHIMemory(FOutputDevice& OutputDevice)
{
TArray<FStatMessage> Stats;
GetPermanentStats(Stats);
FName NAME_STATGROUP_RHI(FStatGroup_STATGROUP_RHI::GetGroupName());
OutputDevice.Logf(TEXT("RHI resource memory (not tracked by our allocator)"));
int64 TotalMemory = 0;
for (int32 Index = 0; Index < Stats.Num(); Index++)
{
FStatMessage const& Meta = Stats[Index];
FName LastGroup = Meta.NameAndInfo.GetGroupName();
if (LastGroup == NAME_STATGROUP_RHI && Meta.NameAndInfo.GetFlag(EStatMetaFlags::IsMemory))
{
OutputDevice.Logf(TEXT("%s"), *FStatsUtils::DebugPrint(Meta));
TotalMemory += Meta.GetValue_int64();
}
}
OutputDevice.Logf(TEXT("%.3fMB total"), TotalMemory / 1024.f / 1024.f);
}
static FAutoConsoleCommandWithOutputDevice GDumpRHIMemoryCmd(
TEXT("rhi.DumpMemory"),
TEXT("Dumps RHI memory stats to the log"),
FConsoleCommandWithOutputDeviceDelegate::CreateStatic(DumpRHIMemory)
);
#endif
//DO NOT USE THE STATIC FLINEARCOLORS TO INITIALIZE THIS STUFF.
//Static init order is undefined and you will likely end up with bad values on some platforms.
const FClearValueBinding FClearValueBinding::None(EClearBinding::ENoneBound);
const FClearValueBinding FClearValueBinding::Black(FLinearColor(0.0f, 0.0f, 0.0f, 1.0f));
const FClearValueBinding FClearValueBinding::BlackMaxAlpha(FLinearColor(0.0f, 0.0f, 0.0f, FLT_MAX));
const FClearValueBinding FClearValueBinding::White(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f));
const FClearValueBinding FClearValueBinding::Transparent(FLinearColor(0.0f, 0.0f, 0.0f, 0.0f));
const FClearValueBinding FClearValueBinding::DepthOne(1.0f, 0);
const FClearValueBinding FClearValueBinding::DepthZero(0.0f, 0);
const FClearValueBinding FClearValueBinding::DepthNear((float)ERHIZBuffer::NearPlane, 0);
const FClearValueBinding FClearValueBinding::DepthFar((float)ERHIZBuffer::FarPlane, 0);
const FClearValueBinding FClearValueBinding::Green(FLinearColor(0.0f, 1.0f, 0.0f, 1.0f));
// Note: this is used as the default normal for DBuffer decals. It must decode to a value of 0 in DecodeDBufferData.
const FClearValueBinding FClearValueBinding::DefaultNormal8Bit(FLinearColor(128.0f / 255.0f, 128.0f / 255.0f, 128.0f / 255.0f, 1.0f));
#if HAS_GPU_STATS
FRHIDrawStatsCategory::FRHIDrawStatsCategory()
: Name(NAME_None)
, Index(-1)
{}
FRHIDrawStatsCategory::FRHIDrawStatsCategory(FName InName)
: Name(InName)
, Index(GetManager().NumCategory++)
{
check(Index < MAX_DRAWCALL_CATEGORY);
if (Index < MAX_DRAWCALL_CATEGORY)
{
GetManager().Array[Index] = this;
}
}
FRHIDrawStatsCategory::FManager::FManager()
: NumCategory(0)
{
FMemory::Memzero(Array);
FMemory::Memzero(DisplayCounts);
}
FRHIDrawStatsCategory::FManager& FRHIDrawStatsCategory::GetManager()
{
// Categories are global scope objects, so the initialization order is undefined.
// Lazy init the manager on first use.
static FManager Manager;
return Manager;
}
#endif
TRefCountPtr<FRHITexture> FRHITextureReference::DefaultTexture;
// This is necessary to get expected results for code that zeros, assigns and then CRC's the whole struct.
//
// See: https://en.cppreference.com/w/cpp/types/has_unique_object_representations
// "This trait was introduced to make it possible to determine whether a type can be correctly hashed by hashing its object representation as a byte array."
static_assert(std::has_unique_object_representations_v<FVertexElement>, "FVertexElement should not have compiler-injected padding");
FString FVertexElement::ToString() const
{
return FString::Printf(TEXT("<%u %u %u %u %u %u>")
, uint32(StreamIndex)
, uint32(Offset)
, uint32(Type)
, uint32(AttributeIndex)
, uint32(Stride)
, uint32(bUseInstanceIndex)
);
}
void FVertexElement::FromString(const FString& InSrc)
{
FromString(FStringView(InSrc));
}
void FVertexElement::FromString(const FStringView& InSrc)
{
constexpr int32 PartCount = 6;
TArray<FStringView, TInlineAllocator<PartCount>> Parts;
UE::String::ParseTokensMultiple(InSrc.TrimStartAndEnd(), {TEXT('\r'), TEXT('\n'), TEXT('\t'), TEXT('<'), TEXT('>'), TEXT(' ')},
[&Parts](FStringView Part) { if (!Part.IsEmpty()) { Parts.Add(Part); } });
check(Parts.Num() == PartCount && sizeof(Type) == 1); //not a very robust parser
const FStringView* PartIt = Parts.GetData();
LexFromString(StreamIndex, *PartIt++);
LexFromString(Offset, *PartIt++);
LexFromString((uint8&)Type, *PartIt++);
LexFromString(AttributeIndex, *PartIt++);
LexFromString(Stride, *PartIt++);
LexFromString(bUseInstanceIndex, *PartIt++);
check(Parts.GetData() + PartCount == PartIt);
}
uint32 GetTypeHash(const FSamplerStateInitializerRHI& Initializer)
{
uint32 Hash = GetTypeHash(Initializer.Filter);
Hash = HashCombine(Hash, GetTypeHash(Initializer.AddressU));
Hash = HashCombine(Hash, GetTypeHash(Initializer.AddressV));
Hash = HashCombine(Hash, GetTypeHash(Initializer.AddressW));
Hash = HashCombine(Hash, GetTypeHash(Initializer.MipBias));
Hash = HashCombine(Hash, GetTypeHash(Initializer.MinMipLevel));
Hash = HashCombine(Hash, GetTypeHash(Initializer.MaxMipLevel));
Hash = HashCombine(Hash, GetTypeHash(Initializer.MaxAnisotropy));
Hash = HashCombine(Hash, GetTypeHash(Initializer.BorderColor));
Hash = HashCombine(Hash, GetTypeHash(Initializer.SamplerComparisonFunction));
return Hash;
}
bool operator== (const FSamplerStateInitializerRHI& A, const FSamplerStateInitializerRHI& B)
{
bool bSame =
A.Filter == B.Filter &&
A.AddressU == B.AddressU &&
A.AddressV == B.AddressV &&
A.AddressW == B.AddressW &&
A.MipBias == B.MipBias &&
A.MinMipLevel == B.MinMipLevel &&
A.MaxMipLevel == B.MaxMipLevel &&
A.MaxAnisotropy == B.MaxAnisotropy &&
A.BorderColor == B.BorderColor &&
A.SamplerComparisonFunction == B.SamplerComparisonFunction;
return bSame;
}
uint32 GetTypeHash(const FRasterizerStateInitializerRHI& Initializer)
{
uint32 Hash = GetTypeHash(Initializer.FillMode);
Hash = HashCombine(Hash, GetTypeHash(Initializer.CullMode));
Hash = HashCombine(Hash, GetTypeHash(Initializer.DepthBias));
Hash = HashCombine(Hash, GetTypeHash(Initializer.SlopeScaleDepthBias));
Hash = HashCombine(Hash, GetTypeHash(Initializer.DepthClipMode));
Hash = HashCombine(Hash, GetTypeHash(Initializer.bAllowMSAA));
return Hash;
}
bool operator== (const FRasterizerStateInitializerRHI& A, const FRasterizerStateInitializerRHI& B)
{
bool bSame =
A.FillMode == B.FillMode &&
A.CullMode == B.CullMode &&
A.DepthBias == B.DepthBias &&
A.SlopeScaleDepthBias == B.SlopeScaleDepthBias &&
A.DepthClipMode == B.DepthClipMode &&
A.bAllowMSAA == B.bAllowMSAA;
return bSame;
}
uint32 GetTypeHash(const FDepthStencilStateInitializerRHI& Initializer)
{
uint32 Hash = GetTypeHash(Initializer.bEnableDepthWrite);
Hash = HashCombine(Hash, GetTypeHash(Initializer.DepthTest));
Hash = HashCombine(Hash, GetTypeHash(Initializer.bEnableFrontFaceStencil));
Hash = HashCombine(Hash, GetTypeHash(Initializer.FrontFaceStencilTest));
Hash = HashCombine(Hash, GetTypeHash(Initializer.FrontFaceStencilFailStencilOp));
Hash = HashCombine(Hash, GetTypeHash(Initializer.FrontFaceDepthFailStencilOp));
Hash = HashCombine(Hash, GetTypeHash(Initializer.FrontFacePassStencilOp));
Hash = HashCombine(Hash, GetTypeHash(Initializer.bEnableBackFaceStencil));
Hash = HashCombine(Hash, GetTypeHash(Initializer.BackFaceStencilTest));
Hash = HashCombine(Hash, GetTypeHash(Initializer.BackFaceStencilFailStencilOp));
Hash = HashCombine(Hash, GetTypeHash(Initializer.BackFaceDepthFailStencilOp));
Hash = HashCombine(Hash, GetTypeHash(Initializer.BackFacePassStencilOp));
Hash = HashCombine(Hash, GetTypeHash(Initializer.StencilReadMask));
Hash = HashCombine(Hash, GetTypeHash(Initializer.StencilWriteMask));
return Hash;
}
bool operator== (const FDepthStencilStateInitializerRHI& A, const FDepthStencilStateInitializerRHI& B)
{
bool bSame =
A.bEnableDepthWrite == B.bEnableDepthWrite &&
A.DepthTest == B.DepthTest &&
A.bEnableFrontFaceStencil == B.bEnableFrontFaceStencil &&
A.FrontFaceStencilTest == B.FrontFaceStencilTest &&
A.FrontFaceStencilFailStencilOp == B.FrontFaceStencilFailStencilOp &&
A.FrontFaceDepthFailStencilOp == B.FrontFaceDepthFailStencilOp &&
A.FrontFacePassStencilOp == B.FrontFacePassStencilOp &&
A.bEnableBackFaceStencil == B.bEnableBackFaceStencil &&
A.BackFaceStencilTest == B.BackFaceStencilTest &&
A.BackFaceStencilFailStencilOp == B.BackFaceStencilFailStencilOp &&
A.BackFaceDepthFailStencilOp == B.BackFaceDepthFailStencilOp &&
A.BackFacePassStencilOp == B.BackFacePassStencilOp &&
A.StencilReadMask == B.StencilReadMask &&
A.StencilWriteMask == B.StencilWriteMask;
return bSame;
}
FString FDepthStencilStateInitializerRHI::ToString() const
{
return
FString::Printf(TEXT("<%u %u ")
, uint32(!!bEnableDepthWrite)
, uint32(DepthTest)
)
+ FString::Printf(TEXT("%u %u %u %u %u ")
, uint32(!!bEnableFrontFaceStencil)
, uint32(FrontFaceStencilTest)
, uint32(FrontFaceStencilFailStencilOp)
, uint32(FrontFaceDepthFailStencilOp)
, uint32(FrontFacePassStencilOp)
)
+ FString::Printf(TEXT("%u %u %u %u %u ")
, uint32(!!bEnableBackFaceStencil)
, uint32(BackFaceStencilTest)
, uint32(BackFaceStencilFailStencilOp)
, uint32(BackFaceDepthFailStencilOp)
, uint32(BackFacePassStencilOp)
)
+ FString::Printf(TEXT("%u %u>")
, uint32(StencilReadMask)
, uint32(StencilWriteMask)
);
}
void FDepthStencilStateInitializerRHI::FromString(const FString& InSrc)
{
FromString(FStringView(InSrc));
}
void FDepthStencilStateInitializerRHI::FromString(const FStringView& InSrc)
{
constexpr int32 PartCount = 14;
TArray<FStringView, TInlineAllocator<PartCount>> Parts;
UE::String::ParseTokensMultiple(InSrc.TrimStartAndEnd(), {TEXT('\r'), TEXT('\n'), TEXT('\t'), TEXT('<'), TEXT('>'), TEXT(' ')},
[&Parts](FStringView Part) { if (!Part.IsEmpty()) { Parts.Add(Part); } });
check(Parts.Num() == PartCount && sizeof(bool) == 1 && sizeof(FrontFaceStencilFailStencilOp) == 1 && sizeof(BackFaceStencilTest) == 1 && sizeof(BackFaceDepthFailStencilOp) == 1); //not a very robust parser
const FStringView* PartIt = Parts.GetData();
LexFromString((uint8&)bEnableDepthWrite, *PartIt++);
LexFromString((uint8&)DepthTest, *PartIt++);
LexFromString((uint8&)bEnableFrontFaceStencil, *PartIt++);
LexFromString((uint8&)FrontFaceStencilTest, *PartIt++);
LexFromString((uint8&)FrontFaceStencilFailStencilOp, *PartIt++);
LexFromString((uint8&)FrontFaceDepthFailStencilOp, *PartIt++);
LexFromString((uint8&)FrontFacePassStencilOp, *PartIt++);
LexFromString((uint8&)bEnableBackFaceStencil, *PartIt++);
LexFromString((uint8&)BackFaceStencilTest, *PartIt++);
LexFromString((uint8&)BackFaceStencilFailStencilOp, *PartIt++);
LexFromString((uint8&)BackFaceDepthFailStencilOp, *PartIt++);
LexFromString((uint8&)BackFacePassStencilOp, *PartIt++);
LexFromString(StencilReadMask, *PartIt++);
LexFromString(StencilWriteMask, *PartIt++);
check(Parts.GetData() + PartCount == PartIt);
}
FString FBlendStateInitializerRHI::ToString() const
{
FString Result = TEXT("<");
for (int32 Index = 0; Index < MaxSimultaneousRenderTargets; Index++)
{
Result += RenderTargets[Index].ToString();
}
Result += FString::Printf(TEXT("%d %d>"), uint32(!!bUseIndependentRenderTargetBlendStates), uint32(!!bUseAlphaToCoverage));
return Result;
}
void FBlendStateInitializerRHI::FromString(const FString& InSrc)
{
FromString(FStringView(InSrc));
}
void FBlendStateInitializerRHI::FromString(const FStringView& InSrc)
{
// files written before bUseAlphaToCoverage change (added in CL 13846572) have one less part
constexpr int32 BackwardCompatiblePartCount = MaxSimultaneousRenderTargets * FRenderTarget::NUM_STRING_FIELDS + 1;
constexpr int32 PartCount = BackwardCompatiblePartCount + 1;
TArray<FStringView, TInlineAllocator<PartCount>> Parts;
UE::String::ParseTokensMultiple(InSrc.TrimStartAndEnd(), {TEXT('\r'), TEXT('\n'), TEXT('\t'), TEXT('<'), TEXT('>'), TEXT(' ')},
[&Parts](FStringView Part) { if (!Part.IsEmpty()) { Parts.Add(Part); } });
checkf((Parts.Num() == PartCount || Parts.Num() == BackwardCompatiblePartCount) && sizeof(bool) == 1,
TEXT("Expecting %d (or %d, for an older format) parts in the blendstate string, got %d"), PartCount, BackwardCompatiblePartCount, Parts.Num()); //not a very robust parser
bool bHasAlphaToCoverageField = Parts.Num() == PartCount;
const FStringView* PartIt = Parts.GetData();
for (int32 Index = 0; Index < MaxSimultaneousRenderTargets; Index++)
{
RenderTargets[Index].FromString(MakeArrayView(PartIt, FRenderTarget::NUM_STRING_FIELDS));
PartIt += FRenderTarget::NUM_STRING_FIELDS;
}
LexFromString((int8&)bUseIndependentRenderTargetBlendStates, *PartIt++);
if (bHasAlphaToCoverageField)
{
LexFromString((int8&)bUseAlphaToCoverage, *PartIt++);
check(Parts.GetData() + PartCount == PartIt);
}
else
{
bUseAlphaToCoverage = false;
check(Parts.GetData() + BackwardCompatiblePartCount == PartIt);
}
}
uint32 GetTypeHash(const FBlendStateInitializerRHI& Initializer)
{
uint32 Hash = GetTypeHash(Initializer.bUseIndependentRenderTargetBlendStates);
Hash = HashCombine(Hash, Initializer.bUseAlphaToCoverage);
for (int32 i = 0; i < MaxSimultaneousRenderTargets; ++i)
{
Hash = HashCombine(Hash, GetTypeHash(Initializer.RenderTargets[i]));
}
return Hash;
}
bool operator== (const FBlendStateInitializerRHI& A, const FBlendStateInitializerRHI& B)
{
bool bSame = A.bUseIndependentRenderTargetBlendStates == B.bUseIndependentRenderTargetBlendStates;
bSame = bSame && A.bUseAlphaToCoverage == B.bUseAlphaToCoverage;
for (int32 i = 0; i < MaxSimultaneousRenderTargets && bSame; ++i)
{
bSame = bSame && A.RenderTargets[i] == B.RenderTargets[i];
}
return bSame;
}
FString FBlendStateInitializerRHI::FRenderTarget::ToString() const
{
return FString::Printf(TEXT("%u %u %u %u %u %u %u ")
, uint32(ColorBlendOp)
, uint32(ColorSrcBlend)
, uint32(ColorDestBlend)
, uint32(AlphaBlendOp)
, uint32(AlphaSrcBlend)
, uint32(AlphaDestBlend)
, uint32(ColorWriteMask)
);
}
void FBlendStateInitializerRHI::FRenderTarget::FromString(const TArray<FString>& Parts, int32 Index)
{
check(Index + NUM_STRING_FIELDS <= Parts.Num());
LexFromString((uint8&)ColorBlendOp, *Parts[Index++]);
LexFromString((uint8&)ColorSrcBlend, *Parts[Index++]);
LexFromString((uint8&)ColorDestBlend, *Parts[Index++]);
LexFromString((uint8&)AlphaBlendOp, *Parts[Index++]);
LexFromString((uint8&)AlphaSrcBlend, *Parts[Index++]);
LexFromString((uint8&)AlphaDestBlend, *Parts[Index++]);
LexFromString((uint8&)ColorWriteMask, *Parts[Index++]);
}
void FBlendStateInitializerRHI::FRenderTarget::FromString(TArrayView<const FStringView> Parts)
{
check(Parts.Num() == NUM_STRING_FIELDS);
const FStringView* PartIt = Parts.GetData();
LexFromString((uint8&)ColorBlendOp, *PartIt++);
LexFromString((uint8&)ColorSrcBlend, *PartIt++);
LexFromString((uint8&)ColorDestBlend, *PartIt++);
LexFromString((uint8&)AlphaBlendOp, *PartIt++);
LexFromString((uint8&)AlphaSrcBlend, *PartIt++);
LexFromString((uint8&)AlphaDestBlend, *PartIt++);
LexFromString((uint8&)ColorWriteMask, *PartIt++);
}
uint32 GetTypeHash(const FBlendStateInitializerRHI::FRenderTarget& Initializer)
{
uint32 Hash = GetTypeHash(Initializer.ColorBlendOp);
Hash = HashCombine(Hash, GetTypeHash(Initializer.ColorDestBlend));
Hash = HashCombine(Hash, GetTypeHash(Initializer.ColorSrcBlend));
Hash = HashCombine(Hash, GetTypeHash(Initializer.AlphaBlendOp));
Hash = HashCombine(Hash, GetTypeHash(Initializer.AlphaDestBlend));
Hash = HashCombine(Hash, GetTypeHash(Initializer.AlphaSrcBlend));
Hash = HashCombine(Hash, GetTypeHash(Initializer.ColorWriteMask));
return Hash;
}
bool operator==(const FBlendStateInitializerRHI::FRenderTarget& A, const FBlendStateInitializerRHI::FRenderTarget& B)
{
bool bSame =
A.ColorBlendOp == B.ColorBlendOp &&
A.ColorDestBlend == B.ColorDestBlend &&
A.ColorSrcBlend == B.ColorSrcBlend &&
A.AlphaBlendOp == B.AlphaBlendOp &&
A.AlphaDestBlend == B.AlphaDestBlend &&
A.AlphaSrcBlend == B.AlphaSrcBlend &&
A.ColorWriteMask == B.ColorWriteMask;
return bSame;
}
#if RHI_ENABLE_RESOURCE_INFO
static FCriticalSection GRHIResourceTrackingCriticalSection;
static TSet<FRHIResource*> GRHITrackedResources;
static bool GRHITrackingResources = false;
bool FRHIResource::GetResourceInfo(FRHIResourceInfo& OutResourceInfo) const
{
OutResourceInfo = FRHIResourceInfo{};
return false;
}
void FRHIResource::BeginTrackingResource(FRHIResource* InResource)
{
if (GRHITrackingResources)
{
LLM_SCOPE_BYNAME(TEXT("RHIMisc/ResourceTracking"));
LLM_TAGSET_SCOPE_CLEAR(ELLMTagSet::Assets);
LLM_TAGSET_SCOPE_CLEAR(ELLMTagSet::AssetClasses);
UE_TRACE_METADATA_CLEAR_SCOPE();
FScopeLock Lock(&GRHIResourceTrackingCriticalSection);
InResource->bBeingTracked = true;
GRHITrackedResources.Add(InResource);
}
}
void FRHIResource::EndTrackingResource(FRHIResource* InResource)
{
if (InResource->bBeingTracked)
{
LLM_SCOPE_BYNAME(TEXT("RHIMisc/ResourceTracking"));
LLM_TAGSET_SCOPE_CLEAR(ELLMTagSet::Assets);
LLM_TAGSET_SCOPE_CLEAR(ELLMTagSet::AssetClasses);
UE_TRACE_METADATA_CLEAR_SCOPE();
FScopeLock Lock(&GRHIResourceTrackingCriticalSection);
GRHITrackedResources.Remove(InResource);
InResource->bBeingTracked = false;
}
}
void FRHIResource::StartTrackingAllResources()
{
GRHITrackingResources = true;
}
void FRHIResource::StopTrackingAllResources()
{
LLM_SCOPE_BYNAME(TEXT("RHIMisc/ResourceTracking"));
LLM_TAGSET_SCOPE_CLEAR(ELLMTagSet::Assets);
LLM_TAGSET_SCOPE_CLEAR(ELLMTagSet::AssetClasses);
UE_TRACE_METADATA_CLEAR_SCOPE();
FScopeLock Lock(&GRHIResourceTrackingCriticalSection);
for (FRHIResource* Resource : GRHITrackedResources)
{
if (Resource)
{
Resource->bBeingTracked = false;
}
}
GRHITrackedResources.Empty();
GRHITrackingResources = false;
}
enum class EBooleanFilter
{
No,
Yes,
All
};
static EBooleanFilter ParseBooleanFilter(const FString& InText)
{
if (InText.Equals(TEXT("No"), ESearchCase::IgnoreCase))
{
return EBooleanFilter::No;
}
if (InText.Equals(TEXT("Yes"), ESearchCase::IgnoreCase))
{
return EBooleanFilter::Yes;
}
if (InText.Equals(TEXT("All"), ESearchCase::IgnoreCase))
{
return EBooleanFilter::All;
}
return EBooleanFilter::No;
}
static FAutoConsoleCommandWithWorldArgsAndOutputDevice GDumpRHIResourceCountsCmd(
TEXT("rhi.DumpResourceCounts"),
TEXT("Dumps RHI resource counts to the log"),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic([](const TArray<FString>& Args, UWorld*, FOutputDevice& OutputDevice)
{
int32 ResourceCounts[RRT_Num]{};
int32 TotalResources{};
FRHIResourceInfo ResourceInfo;
{
FScopeLock Lock(&GRHIResourceTrackingCriticalSection);
TotalResources = GRHITrackedResources.Num();
for (const FRHIResource* Resource : GRHITrackedResources)
{
if (Resource)
{
ERHIResourceType ResourceType = Resource->GetType();
if (ResourceType > 0 && ResourceType < RRT_Num)
{
ResourceCounts[ResourceType]++;
}
}
}
}
FBufferedOutputDevice BufferedOutput;
FName CategoryName(TEXT("RHIResources"));
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("RHIResource Counts"));
for (int32 Index = 0; Index < RRT_Num; Index++)
{
const int32 CurrentCount = ResourceCounts[Index];
if (CurrentCount > 0)
{
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("%s: %d"),
StringFromRHIResourceType((ERHIResourceType)Index),
CurrentCount);
}
}
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("Total: %d"), TotalResources);
BufferedOutput.RedirectTo(OutputDevice);
}));
namespace RHIInternal
{
struct FResourceEntry
{
const FRHIResource* Resource;
FRHIResourceInfo ResourceInfo;
};
struct FResourceFlags
{
bool bResident = false;
bool bMarkedForDelete = false;
bool bTransient = false;
bool bStreaming = false;
bool bRT = false;
bool bDS = false;
bool bUAV = false;
bool bRTAS = false;
bool bHasFlags = false;
FString GetString()
{
FString FlagsString;
bool bHasFlag = false;
if (bResident)
{
FlagsString += "Resident";
bHasFlag = true;
}
if (bMarkedForDelete)
{
FlagsString += bHasFlag ? " | MarkedForDelete" : "MarkedForDelete";
bHasFlag = true;
}
if (bTransient)
{
FlagsString += bHasFlag ? " | Transient" : "Transient";
bHasFlag = true;
}
if (bStreaming)
{
FlagsString += bHasFlag ? " | Streaming" : "Streaming";
bHasFlag = true;
}
if (bRT)
{
FlagsString += bHasFlag ? " | RT" : "RT";
bHasFlag = true;
}
else if (bDS)
{
FlagsString += bHasFlag ? " | DS" : "DS";
bHasFlag = true;
}
if (bUAV)
{
FlagsString += bHasFlag ? " | UAV" : "UAV";
bHasFlag = true;
}
if (bRTAS)
{
FlagsString += bHasFlag ? " | RTAS" : "RTAS";
bHasFlag = true;
}
return FlagsString;
}
};
void GetTrackedResourcesInternal(const FString& NameFilter, ERHIResourceType TypeFilter, EBooleanFilter TransientFilter, TArray<FResourceEntry>& OutResources, int32& OutNumberOfResourcesToShow,
int32& OutTotalResourcesWithInfo, int32& OutTotalTrackedResources, int64& OutTotalTrackedResourceSize, int64& OutTotalTrackedTransientResourceSize)
{
TRACE_CPUPROFILER_EVENT_SCOPE(RHIInternal_GetTrackedResourcesInternal);
TCHAR ResourceNameBuffer[FName::StringBufferSize];
auto ShouldIncludeResource = [&](const FRHIResource* Resource, const FRHIResourceInfo& ResourceInfo) -> bool
{
if (!NameFilter.IsEmpty())
{
if (ResourceInfo.Name.ToString(ResourceNameBuffer) == 0 || UE::String::FindFirst(ResourceNameBuffer, *NameFilter, ESearchCase::IgnoreCase) == INDEX_NONE)
{
return false;
}
}
if (TypeFilter != RRT_None)
{
if (TypeFilter == RRT_Texture)
{
if (ResourceInfo.Type != RRT_Texture2D &&
ResourceInfo.Type != RRT_Texture2DArray &&
ResourceInfo.Type != RRT_Texture3D &&
ResourceInfo.Type != RRT_TextureCube)
{
return false;
}
}
else if (TypeFilter != ResourceInfo.Type)
{
return false;
}
}
if (TransientFilter != EBooleanFilter::All)
{
const bool bAllowedFlag = TransientFilter == EBooleanFilter::Yes ? true : false;
if (ResourceInfo.IsTransient != bAllowedFlag)
{
return false;
}
}
return true;
};
OutResources.Reset();
{
FRHIResourceInfo ResourceInfo;
OutTotalTrackedResources = GRHITrackedResources.Num();
for (const FRHIResource* Resource : GRHITrackedResources)
{
if (Resource && Resource->GetResourceInfo(ResourceInfo))
{
ResourceInfo.bValid = Resource->IsValid();
if (ShouldIncludeResource(Resource, ResourceInfo))
{
OutResources.Emplace(FResourceEntry{ Resource, ResourceInfo });
}
OutTotalResourcesWithInfo++;
if (ResourceInfo.IsTransient)
{
OutTotalTrackedTransientResourceSize += ResourceInfo.VRamAllocation.AllocationSize;
}
else
{
OutTotalTrackedResourceSize += ResourceInfo.VRamAllocation.AllocationSize;
}
}
}
}
if (OutNumberOfResourcesToShow < 0 || OutNumberOfResourcesToShow > OutResources.Num())
{
OutNumberOfResourcesToShow = OutResources.Num();
}
OutResources.Sort([](const FResourceEntry& EntryA, const FResourceEntry& EntryB)
{
return EntryA.ResourceInfo.VRamAllocation.AllocationSize > EntryB.ResourceInfo.VRamAllocation.AllocationSize;
});
}
FResourceFlags GetResourceFlagsInternal(const FResourceEntry& Resource)
{
FResourceFlags Flags;
Flags.bResident = Resource.ResourceInfo.bResident;
Flags.bMarkedForDelete = !Resource.ResourceInfo.bValid;
Flags.bTransient = Resource.ResourceInfo.IsTransient;
bool bIsTexture = Resource.ResourceInfo.Type == RRT_Texture ||
Resource.ResourceInfo.Type == RRT_Texture2D ||
Resource.ResourceInfo.Type == RRT_Texture2DArray ||
Resource.ResourceInfo.Type == RRT_Texture3D ||
Resource.ResourceInfo.Type == RRT_TextureCube;
if (bIsTexture)
{
FRHITexture* Texture = (FRHITexture*)Resource.Resource;
Flags.bRT = EnumHasAnyFlags(Texture->GetFlags(), TexCreate_RenderTargetable);
Flags.bDS = EnumHasAnyFlags(Texture->GetFlags(), TexCreate_DepthStencilTargetable);
Flags.bUAV = EnumHasAnyFlags(Texture->GetFlags(), TexCreate_UAV);
Flags.bStreaming = EnumHasAnyFlags(Texture->GetFlags(), TexCreate_Streamable);
}
else if (Resource.ResourceInfo.Type == RRT_Buffer)
{
FRHIBuffer* Buffer = (FRHIBuffer*)Resource.Resource;
Flags.bUAV = EnumHasAnyFlags((EBufferUsageFlags)Buffer->GetUsage(), BUF_UnorderedAccess);
Flags.bRTAS = EnumHasAnyFlags((EBufferUsageFlags)Buffer->GetUsage(), BUF_AccelerationStructure);
}
Flags.bHasFlags = Flags.bResident || Flags.bMarkedForDelete || Flags.bTransient || Flags.bStreaming || Flags.bRT || Flags.bDS || Flags.bUAV || Flags.bRTAS;
return Flags;
}
}
void RHIGetTrackedResourceStats(TArray<TSharedPtr<FRHIResourceStats>>& OutResourceStats)
{
TRACE_CPUPROFILER_EVENT_SCOPE(RHIGetTrackedResourceStats);
FScopeLock Lock(&GRHIResourceTrackingCriticalSection);
TArray<RHIInternal::FResourceEntry> Resources;
int32 TotalResourcesWithInfo = 0;
int32 TotalTrackedResources = 0;
int64 TotalTrackedResourceSize = 0;
int64 TotalTrackedTransientResourceSize = 0;
int32 NumberOfResourcesToShow = -1;
RHIInternal::GetTrackedResourcesInternal(TEXT(""), ERHIResourceType::RRT_None, EBooleanFilter::All, Resources, NumberOfResourcesToShow, TotalResourcesWithInfo, TotalTrackedResources, TotalTrackedResourceSize, TotalTrackedTransientResourceSize);
OutResourceStats.SetNum(Resources.Num());
ParallelFor(Resources.Num(), [&](int32 Index)
{
const FRHIResource* Resource = Resources[Index].Resource;
const FRHIResourceInfo& ResourceInfo = Resources[Index].ResourceInfo;
const TCHAR* ResourceType = StringFromRHIResourceType(ResourceInfo.Type);
const int64 SizeInBytes = ResourceInfo.VRamAllocation.AllocationSize;
RHIInternal::FResourceFlags Flags = GetResourceFlagsInternal(Resources[Index]);
OutResourceStats[Index] = MakeShared<FRHIResourceStats>(ResourceInfo.Name, Resource->GetOwnerName(), ResourceType, Flags.GetString(), SizeInBytes,
Flags.bResident, Flags.bMarkedForDelete, Flags.bTransient, Flags.bStreaming, Flags.bRT, Flags.bDS, Flags.bUAV, Flags.bRTAS, Flags.bHasFlags);
});
}
void RHIDumpResourceMemory(const FString& NameFilter, ERHIResourceType TypeFilter, EBooleanFilter TransientFilter, int32 NumberOfResourcesToShow, bool bUseCSVOutput, bool bSummaryOutput, bool bOutputToCSVFile, FBufferedOutputDevice& BufferedOutput)
{
FArchive* CSVFile{ nullptr };
if (bOutputToCSVFile)
{
const FString Filename = FString::Printf(TEXT("%srhiDumpResourceMemory-%s.csv"), *FPaths::ProfilingDir(), *FDateTime::Now().ToString());
CSVFile = IFileManager::Get().CreateFileWriter(*Filename, FILEWRITE_AllowRead);
}
FScopeLock Lock(&GRHIResourceTrackingCriticalSection);
TArray<RHIInternal::FResourceEntry> Resources;
int32 TotalResourcesWithInfo = 0;
int32 TotalTrackedResources = 0;
int64 TotalTrackedResourceSize = 0;
int64 TotalTrackedTransientResourceSize = 0;
RHIInternal::GetTrackedResourcesInternal(NameFilter, TypeFilter, TransientFilter, Resources, NumberOfResourcesToShow, TotalResourcesWithInfo, TotalTrackedResources, TotalTrackedResourceSize, TotalTrackedTransientResourceSize);
const int32 NumberOfResourcesBeforeNumberFilter = Resources.Num();
FName CategoryName(TEXT("RHIResources"));
if (bOutputToCSVFile)
{
const TCHAR* Header = TEXT("Name,Type,Size,Resident,MarkedForDelete,Transient,Streaming,RenderTarget,UAV,\"Raytracing Acceleration Structure\",Owner\n");
CSVFile->Serialize(TCHAR_TO_ANSI(Header), FPlatformString::Strlen(Header));
}
else if (bUseCSVOutput)
{
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("Name,Type,Size,Resident,MarkedForDelete,Transient,Streaming,RenderTarget,UAV,\"Raytracing Acceleration Structure\",Owner"));
}
else
{
if (bSummaryOutput == false)
{
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("Tracked RHIResources (%d total with info, %d total tracked)"), TotalResourcesWithInfo, TotalTrackedResources);
}
if (NumberOfResourcesToShow != NumberOfResourcesBeforeNumberFilter)
{
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("Showing %d of %d matched resources"), NumberOfResourcesToShow, NumberOfResourcesBeforeNumberFilter);
}
}
TCHAR ResourceNameBuffer[FName::StringBufferSize];
TCHAR ResourceOwnerBuffer[FName::StringBufferSize];
int64 TotalShownResourceSize = 0;
for (int32 Index = 0; Index < Resources.Num(); Index++)
{
if (Index < NumberOfResourcesToShow)
{
const FRHIResourceInfo& ResourceInfo = Resources[Index].ResourceInfo;
ResourceInfo.Name.ToString(ResourceNameBuffer);
const TCHAR* ResourceType = StringFromRHIResourceType(ResourceInfo.Type);
const int64 SizeInBytes = ResourceInfo.VRamAllocation.AllocationSize;
Resources[Index].Resource->GetOwnerName().ToString(ResourceOwnerBuffer);
RHIInternal::FResourceFlags Flags = GetResourceFlagsInternal(Resources[Index]);
if (bSummaryOutput == false)
{
if (bOutputToCSVFile || bUseCSVOutput)
{
const FString Row = FString::Printf(TEXT("%s,%s,%.9f,%s,%s,%s,%s,%s,%s,%s,%s\n"),
ResourceNameBuffer,
ResourceType,
SizeInBytes / double(1 << 20),
Flags.bResident ? TEXT("Yes") : TEXT("No"),
Flags.bMarkedForDelete ? TEXT("Yes") : TEXT("No"),
Flags.bTransient ? TEXT("Yes") : TEXT("No"),
Flags.bStreaming ? TEXT("Yes") : TEXT("No"),
(Flags.bRT || Flags.bDS) ? TEXT("Yes") : TEXT("No"),
Flags.bUAV ? TEXT("Yes") : TEXT("No"),
Flags.bRTAS ? TEXT("Yes") : TEXT("No"),
ResourceOwnerBuffer);
if (bOutputToCSVFile)
{
CSVFile->Serialize(TCHAR_TO_ANSI(*Row), Row.Len());
}
else
{
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("%s"), *Row);
}
}
else
{
FString ResoureFlags = Flags.GetString();
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("Name: %s - Type: %s - Size: %.9f MB - Flags: %s - Owner: %s"),
ResourceNameBuffer,
ResourceType,
SizeInBytes / double(1 << 20),
ResoureFlags.IsEmpty() ? TEXT("None") : *ResoureFlags,
ResourceOwnerBuffer);
}
}
TotalShownResourceSize += SizeInBytes;
}
}
if (bOutputToCSVFile)
{
delete CSVFile;
CSVFile = nullptr;
}
else if (!bUseCSVOutput)
{
const double TotalNonTransientSizeF = TotalTrackedResourceSize / double(1 << 20);
const double TotalTransientSizeF = TotalTrackedTransientResourceSize / double(1 << 20);
const double TotalSizeF = (TotalTrackedResourceSize + TotalTrackedTransientResourceSize) / double(1 << 20);
const double ShownSizeF = TotalShownResourceSize / double(1 << 20);
if (NumberOfResourcesToShow != TotalResourcesWithInfo)
{
double TotalSizeToUse = 0.0;
const TCHAR* ExtraText = TEXT("");
if (TransientFilter == EBooleanFilter::No)
{
TotalSizeToUse = TotalNonTransientSizeF;
ExtraText = TEXT(" non-transient");
}
else if (TransientFilter == EBooleanFilter::Yes)
{
TotalSizeToUse = TotalTransientSizeF;
ExtraText = TEXT(" transient");
}
else
{
TotalSizeToUse = TotalSizeF;
ExtraText = TEXT("");
}
if (TotalSizeToUse == 0.0)
{
TotalSizeToUse = TotalSizeF;
}
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("Shown %d entries%s. Size: %.2f/%.2f MB (%.2f%% of total%s)"),
NumberOfResourcesToShow, !NameFilter.IsEmpty() ? *FString::Printf(TEXT(" with name %s"), *NameFilter) : TEXT(""), ShownSizeF, TotalSizeToUse, 100.0 * ShownSizeF / TotalSizeToUse, ExtraText);
}
if (bSummaryOutput == false)
{
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT("Total tracked resource size: %.9f MB"), TotalSizeF);
if (TotalTrackedTransientResourceSize > 0)
{
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT(" Non-Transient: %.9f MB"), TotalNonTransientSizeF);
BufferedOutput.CategorizedLogf(CategoryName, ELogVerbosity::Log, TEXT(" Transient: %.9f MB"), TotalTransientSizeF);
}
}
}
}
static FAutoConsoleCommandWithWorldArgsAndOutputDevice GDumpRHIResourceMemoryCmd(
TEXT("rhi.DumpResourceMemory"),
TEXT("Dumps RHI resource memory stats to the log\n")
TEXT("Usage: rhi.DumpResourceMemory [<Number To Show>] [all] [summary] [Name=<Filter Text>] [Type=<RHI Resource Type>] [Transient=<no, yes, or all> [csv]"),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic([](const TArray<FString>& Args, UWorld*, FOutputDevice& OutputDevice)
{
FString NameFilter;
ERHIResourceType TypeFilter = RRT_None;
EBooleanFilter TransientFilter = EBooleanFilter::No;
int32 NumberOfResourcesToShow = 50;
bool bUseCSVOutput = false;
bool bSummaryOutput = false;
bool bOutputToCSVFile = false;
for (const FString& Argument : Args)
{
if (Argument.Equals(TEXT("all"), ESearchCase::IgnoreCase))
{
NumberOfResourcesToShow = -1;
}
else if (Argument.Equals(TEXT("-csv"), ESearchCase::IgnoreCase))
{
bUseCSVOutput = true;
}
else if (Argument.Equals(TEXT("-csvfile"), ESearchCase::IgnoreCase))
{
bOutputToCSVFile = true;
}
else if (Argument.StartsWith(TEXT("Name="), ESearchCase::IgnoreCase))
{
NameFilter = Argument.RightChop(5);
}
else if (Argument.StartsWith(TEXT("Type="), ESearchCase::IgnoreCase))
{
TypeFilter = RHIResourceTypeFromString(Argument.RightChop(5));
}
else if (Argument.StartsWith(TEXT("Transient="), ESearchCase::IgnoreCase))
{
TransientFilter = ParseBooleanFilter(Argument.RightChop(10));
}
else if (FCString::IsNumeric(*Argument))
{
LexFromString(NumberOfResourcesToShow, *Argument);
}
else if (Argument.Equals(TEXT("summary"), ESearchCase::IgnoreCase))
{
// Respects name, type and transient filters but only reports total sizes.
// Does not report a list of individual resources.
bSummaryOutput = true;
NumberOfResourcesToShow = -1;
}
else
{
NameFilter = Argument;
}
}
FBufferedOutputDevice BufferedOutput;
RHIDumpResourceMemory(NameFilter, TypeFilter, TransientFilter, NumberOfResourcesToShow, bUseCSVOutput, bSummaryOutput, bOutputToCSVFile, BufferedOutput);
BufferedOutput.RedirectTo(OutputDevice);
}));
void RHIDumpResourceMemoryToCSV()
{
FString NameFilter;
ERHIResourceType TypeFilter = RRT_None;
EBooleanFilter TransientFilter = EBooleanFilter::No;
int32 NumberOfResourcesToShow = -1;
bool bUseCSVOutput = false;
bool bSummaryOutput = false;
bool bOutputToCSVFile = true;
FBufferedOutputDevice BufferedOutput;
RHIDumpResourceMemory(NameFilter, TypeFilter, TransientFilter, NumberOfResourcesToShow, bUseCSVOutput, bSummaryOutput, bOutputToCSVFile, BufferedOutput);
}
#endif // RHI_ENABLE_RESOURCE_INFO
static_assert(ERHIZBuffer::FarPlane != ERHIZBuffer::NearPlane, "Near and Far planes must be different!");
static_assert((int32)ERHIZBuffer::NearPlane == 0 || (int32)ERHIZBuffer::NearPlane == 1, "Invalid Values for Near Plane, can only be 0 or 1!");
static_assert((int32)ERHIZBuffer::FarPlane == 0 || (int32)ERHIZBuffer::FarPlane == 1, "Invalid Values for Far Plane, can only be 0 or 1");
/**
* RHI configuration settings.
*/
static TAutoConsoleVariable<int32> GCVarRHIRenderPass(
TEXT("r.RHIRenderPasses"),
0,
TEXT(""),
ECVF_Default);
static TAutoConsoleVariable<int32> CVarGPUCrashOnOutOfMemory(
TEXT("r.GPUCrashOnOutOfMemory"),
0,
TEXT("Enable crash reporting on GPU OOM"),
ECVF_ReadOnly
);
FString GRHIBindlessConfiguration = TEXT("Disabled");
static FAutoConsoleVariableRef CVarEnableBindless(
TEXT("rhi.Bindless"),
GRHIBindlessConfiguration,
TEXT("Set to Enabled to enable for all shader types. Set to RayTracing to restrict to Raytracing shaders."),
ECVF_ReadOnly
);
bool RHIGetShaderPlatformConfigurationInt(int32& OutSetting, FConfigCacheIni* Config, EShaderPlatform Platform, const TCHAR* SettingName)
{
FStringBuilderBase CategoryName;
FDataDrivenShaderPlatformInfo::GetName(Platform).ToString(CategoryName);
if (CategoryName.Len() > 0)
{
CategoryName.InsertAt(0, TEXT("ShaderPlatformConfig "));
return Config->GetInt(*CategoryName, SettingName, OutSetting, GEngineIni);
}
return false;
}
bool RHIGetShaderPlatformConfigurationString(FString& OutSetting, FConfigCacheIni* Config, EShaderPlatform Platform, const TCHAR* SettingName)
{
FStringBuilderBase CategoryName;
FDataDrivenShaderPlatformInfo::GetName(Platform).ToString(CategoryName);
if (CategoryName.Len() > 0)
{
CategoryName.InsertAt(0, TEXT("ShaderPlatformConfig "));
return Config->GetString(*CategoryName, SettingName, OutSetting, GEngineIni);
}
return false;
}
TOptional<ERHIBindlessConfiguration> RHIGetForcedBindlessConfiguration()
{
#if WITH_EDITOR
static const bool bCachedOff = FParse::Param(FCommandLine::Get(), TEXT("BindlessOff"));
if (bCachedOff)
{
return ERHIBindlessConfiguration::Disabled;
}
static const bool bCachedRayTracing = FParse::Param(FCommandLine::Get(), TEXT("BindlessRT"));
if (bCachedRayTracing)
{
return ERHIBindlessConfiguration::RayTracing;
}
static const bool bCachedMinimal = FParse::Param(FCommandLine::Get(), TEXT("BindlessMinimal"));
if (bCachedMinimal)
{
return ERHIBindlessConfiguration::Minimal;
}
static const bool bCachedAll = FParse::Param(FCommandLine::Get(), TEXT("BindlessAll"));
if (bCachedAll)
{
return ERHIBindlessConfiguration::All;
}
// As of 5.7, -Bindless only means "Minimal". This may change in the future to be All.
static const bool bCachedBindless = FParse::Param(FCommandLine::Get(), TEXT("Bindless"));
if (bCachedBindless)
{
return ERHIBindlessConfiguration::Minimal;
}
#endif
return TOptional<ERHIBindlessConfiguration>();
}
ERHIBindlessConfiguration RHIParseBindlessConfiguration(EShaderPlatform Platform, const FString& ConfigSettingString, const FString& CVarSettingString)
{
if (!FDataDrivenShaderPlatformInfo::GetSupportsBindless(Platform))
{
return ERHIBindlessConfiguration::Disabled;
}
if (TOptional<ERHIBindlessConfiguration> ForcedConfig = RHIGetForcedBindlessConfiguration())
{
return ForcedConfig.GetValue();
}
ERHIBindlessConfiguration ConfigSetting = ERHIBindlessConfiguration::Disabled;
GetBindlessConfigurationFromString(ConfigSettingString, ConfigSetting);
ERHIBindlessConfiguration CVarSetting = ERHIBindlessConfiguration::Disabled;
GetBindlessConfigurationFromString(CVarSettingString, CVarSetting);
if (ConfigSetting == ERHIBindlessConfiguration::Disabled && CVarSetting == ERHIBindlessConfiguration::Disabled)
{
return ERHIBindlessConfiguration::Disabled;
}
// CVar should always take precedence over the config setting
return CVarSetting != ERHIBindlessConfiguration::Disabled ? CVarSetting : ConfigSetting;
}
static ERHIBindlessConfiguration DetermineRuntimeBindlessConfiguration(EShaderPlatform Platform, const TCHAR* ConfigName, const FString& CVarSetting)
{
if (!FDataDrivenShaderPlatformInfo::GetSupportsBindless(Platform))
{
return ERHIBindlessConfiguration::Disabled;
}
FString ConfigSetting;
RHIGetShaderPlatformConfigurationString(ConfigSetting, GConfig, Platform, ConfigName);
return RHIParseBindlessConfiguration(Platform, ConfigSetting, CVarSetting);
}
ERHIBindlessConfiguration RHIGetRuntimeBindlessConfiguration(EShaderPlatform Platform)
{
return DetermineRuntimeBindlessConfiguration(Platform, TEXT("BindlessConfiguration"), GRHIBindlessConfiguration);
}
// By default, read only states and UAV states are allowed to participate in state merging.
ERHIAccess GRHIMergeableAccessMask = ERHIAccess::ReadOnlyMask | ERHIAccess::UAVMask;
// By default, only exclusively read only accesses are allowed.
ERHIAccess GRHIMultiPipelineMergeableAccessMask = ERHIAccess::ReadOnlyExclusiveMask;
ERHIAccess GRHIMultiSubresourceDiscardIntermediateAccess = ERHIAccess::SRVCompute;
void FRHIDrawStats::Accumulate(FRHIDrawStats& Other)
{
for (uint32 GPUIndex = 0; GPUIndex < GNumExplicitGPUsForRendering; ++GPUIndex)
{
FPerGPU& LeftGPU = GetGPU(GPUIndex);
FPerGPU& RightGPU = Other.GetGPU(GPUIndex);
for (int32 CategoryIndex = 0; CategoryIndex < NumCategories; ++CategoryIndex)
{
FPerCategory& LeftCategory = LeftGPU.Categories[CategoryIndex];
FPerCategory& RightCategory = RightGPU.Categories[CategoryIndex];
LeftCategory += RightCategory;
}
}
}
RHI_API void FRHIDrawStats::ProcessAsFrameStats()
{
#if HAS_GPU_STATS
// Only copy the display counters every half second keep things more stable.
constexpr float TimeoutSeconds = 0.5;
static double LastTime = 0.0;
double CurrentTime = FPlatformTime::Seconds();
bool bCopyDisplayFrames = false;
if (CurrentTime - LastTime > TimeoutSeconds)
{
LastTime = CurrentTime;
bCopyDisplayFrames = true;
}
FRHIDrawStatsCategory::FManager& Manager = FRHIDrawStatsCategory::GetManager();
#endif
// Summed stats across all GPUs
FPerCategory Total = {};
TStaticArray<FPerCategory, FRHIDrawStats::NumCategories> TotalPerCategory;
FMemory::Memzero(TotalPerCategory);
for (int32 GPUIndex = 0; GPUIndex < MAX_NUM_GPUS; ++GPUIndex)
{
FPerCategory TotalPerGPU = {};
for (int32 CategoryIndex = 0; CategoryIndex < FRHIDrawStats::NumCategories; ++CategoryIndex)
{
FPerCategory& Category = GPUs[GPUIndex].Categories[CategoryIndex];
TotalPerCategory[CategoryIndex] += Category;
TotalPerGPU += Category;
Total += Category;
#if HAS_GPU_STATS
if (bCopyDisplayFrames && CategoryIndex < Manager.NumCategory)
{
Manager.DisplayCounts[CategoryIndex][GPUIndex] = Category.Draws;
}
#endif // HAS_GPU_STATS
}
GNumDrawCallsRHI [GPUIndex] = TotalPerGPU.Draws;
GNumPrimitivesDrawnRHI[GPUIndex] = TotalPerGPU.GetTotalPrimitives();
}
// Multi-GPU support : CSV stats do not support MGPU yet. We're summing the totals across all GPUs here.
CSV_CUSTOM_STAT(RHI, DrawCalls , int32(Total.Draws ), ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(RHI, PrimitivesDrawn, int32(Total.GetTotalPrimitives()), ECsvCustomStatOp::Set);
#if HAS_GPU_STATS
SET_DWORD_STAT(STAT_RHITriangles , Total.Triangles);
SET_DWORD_STAT(STAT_RHILines , Total.Lines );
SET_DWORD_STAT(STAT_RHIDrawPrimitiveCalls, Total.Draws );
#if CSV_PROFILER_STATS
for (int32 CategoryIndex = 0; CategoryIndex < Manager.NumCategory; ++CategoryIndex)
{
FCsvProfiler::RecordCustomStat(Manager.Array[CategoryIndex]->Name, CSV_CATEGORY_INDEX(DrawCall), int32(TotalPerCategory[CategoryIndex].Draws), ECsvCustomStatOp::Set);
}
#endif
#endif // HAS_GPU_STATS
Reset();
}
//
// The current shader platform.
//
EShaderPlatform GMaxRHIShaderPlatform = SP_PCD3D_SM5;
/** The maximum feature level supported on this machine */
ERHIFeatureLevel::Type GMaxRHIFeatureLevel = ERHIFeatureLevel::SM5;
bool IsRHIDeviceAMD()
{
check(GRHIVendorId != 0);
return GRHIVendorId == 0x1002;
}
bool IsRHIDeviceIntel()
{
check(GRHIVendorId != 0);
return GRHIVendorId == 0x8086;
}
bool IsRHIDeviceNVIDIA()
{
check(GRHIVendorId != 0);
return GRHIVendorId == 0x10DE;
}
bool IsRHIDeviceApple()
{
check(GRHIVendorId != 0);
return GRHIVendorId == (uint32) EGpuVendorId::Apple;
}
uint32 RHIGetMetalShaderLanguageVersion(const FStaticShaderPlatform Platform)
{
if (IsMetalPlatform(Platform))
{
if (IsPCPlatform(Platform))
{
static int32 MacMetalShaderLanguageVersion = -1;
if (MacMetalShaderLanguageVersion == -1)
{
if (!GConfig->GetInt(TEXT("/Script/MacTargetPlatform.MacTargetSettings"), TEXT("MetalLanguageVersion"), MacMetalShaderLanguageVersion, GEngineIni))
{
MacMetalShaderLanguageVersion = 0; // 0 means default EMacMetalShaderStandard::MacMetalSLStandard_Minimum
}
}
return MacMetalShaderLanguageVersion;
}
else
{
static int32 IOSMetalShaderLanguageVersion = -1;
if (IOSMetalShaderLanguageVersion == -1)
{
if (!GConfig->GetInt(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("MetalLanguageVersion"), IOSMetalShaderLanguageVersion, GEngineIni))
{
IOSMetalShaderLanguageVersion = 0; // 0 means default EIOSMetalShaderStandard::IOSMetalSLStandard_Minimum
}
}
return IOSMetalShaderLanguageVersion;
}
}
return 0;
}
static ERHIFeatureLevel::Type GRHIMobilePreviewFeatureLevel = ERHIFeatureLevel::Num;
void RHISetMobilePreviewFeatureLevel(ERHIFeatureLevel::Type MobilePreviewFeatureLevel)
{
check(GRHIMobilePreviewFeatureLevel == ERHIFeatureLevel::Num);
check(!GIsEditor);
GRHIMobilePreviewFeatureLevel = MobilePreviewFeatureLevel;
}
bool RHIGetPreviewFeatureLevel(ERHIFeatureLevel::Type& PreviewFeatureLevelOUT)
{
static bool bForceFeatureLevelES3_1 = !GIsEditor && (FParse::Param(FCommandLine::Get(), TEXT("FeatureLevelES31")) || FParse::Param(FCommandLine::Get(), TEXT("FeatureLevelES3_1")));
if (bForceFeatureLevelES3_1)
{
PreviewFeatureLevelOUT = ERHIFeatureLevel::ES3_1;
}
else if (!GIsEditor && GRHIMobilePreviewFeatureLevel != ERHIFeatureLevel::Num)
{
PreviewFeatureLevelOUT = GRHIMobilePreviewFeatureLevel;
}
else
{
return false;
}
return true;
}
EPixelFormat RHIPreferredPixelFormatHint(EPixelFormat PreferredPixelFormat)
{
if (GDynamicRHI)
{
return GDynamicRHI->RHIPreferredPixelFormatHint(PreferredPixelFormat);
}
return PreferredPixelFormat;
}
int32 RHIGetPreferredClearUAVRectPSResourceType(const FStaticShaderPlatform Platform)
{
// We can't bind Nanite buffers as RWBuffer to perform a clear op.
if (IsMetalPlatform(Platform) && !FDataDrivenShaderPlatformInfo::GetSupportsNanite(Platform))
{
static constexpr uint32 METAL_TEXTUREBUFFER_SHADER_LANGUAGE_VERSION = 4;
if (METAL_TEXTUREBUFFER_SHADER_LANGUAGE_VERSION <= RHIGetMetalShaderLanguageVersion(Platform))
{
return 0; // BUFFER
}
}
return 1; // TEXTURE_2D
}
RHI_API bool RHISupportsVolumeTextureRendering(const FStaticShaderPlatform Platform)
{
#if WITH_EDITOR
// When preview platforms are supported (when building with editor) we might be previewing an RHI that doesn't support geometry shaders (such as Metal)
// while rendering with a runtime RHI that doesn't support vertex shader layers as an alternative (such as D3D), so take these DDPI entries into account, too.
return GSupportsVolumeTextureRendering && (RHISupportsGeometryShaders(Platform) || RHISupportsVertexShaderLayer(Platform));
#else
return GSupportsVolumeTextureRendering;
#endif
}
void FRHIRenderPassInfo::ConvertToRenderTargetsInfo(FRHISetRenderTargetsInfo& OutRTInfo) const
{
for (int32 Index = 0; Index < MaxSimultaneousRenderTargets; ++Index)
{
if (!ColorRenderTargets[Index].RenderTarget)
{
break;
}
OutRTInfo.ColorRenderTarget[Index].Texture = ColorRenderTargets[Index].RenderTarget;
ERenderTargetLoadAction LoadAction = GetLoadAction(ColorRenderTargets[Index].Action);
OutRTInfo.ColorRenderTarget[Index].LoadAction = LoadAction;
OutRTInfo.ColorRenderTarget[Index].StoreAction = GetStoreAction(ColorRenderTargets[Index].Action);
OutRTInfo.ColorRenderTarget[Index].ArraySliceIndex = ColorRenderTargets[Index].ArraySlice;
OutRTInfo.ColorRenderTarget[Index].MipIndex = ColorRenderTargets[Index].MipIndex;
++OutRTInfo.NumColorRenderTargets;
OutRTInfo.bClearColor |= (LoadAction == ERenderTargetLoadAction::EClear);
if (ColorRenderTargets[Index].ResolveTarget)
{
OutRTInfo.bHasResolveAttachments = true;
OutRTInfo.ColorResolveRenderTarget[Index] = OutRTInfo.ColorRenderTarget[Index];
OutRTInfo.ColorResolveRenderTarget[Index].Texture = ColorRenderTargets[Index].ResolveTarget;
}
}
ERenderTargetActions DepthActions = GetDepthActions(DepthStencilRenderTarget.Action);
ERenderTargetActions StencilActions = GetStencilActions(DepthStencilRenderTarget.Action);
ERenderTargetLoadAction DepthLoadAction = GetLoadAction(DepthActions);
ERenderTargetStoreAction DepthStoreAction = GetStoreAction(DepthActions);
ERenderTargetLoadAction StencilLoadAction = GetLoadAction(StencilActions);
ERenderTargetStoreAction StencilStoreAction = GetStoreAction(StencilActions);
OutRTInfo.DepthStencilRenderTarget = FRHIDepthRenderTargetView(DepthStencilRenderTarget.DepthStencilTarget,
DepthLoadAction,
GetStoreAction(DepthActions),
StencilLoadAction,
GetStoreAction(StencilActions),
DepthStencilRenderTarget.ExclusiveDepthStencil);
OutRTInfo.bClearDepth = (DepthLoadAction == ERenderTargetLoadAction::EClear);
OutRTInfo.bClearStencil = (StencilLoadAction == ERenderTargetLoadAction::EClear);
if (DepthStencilRenderTarget.ResolveTarget && DepthStencilRenderTarget.ResolveTarget != DepthStencilRenderTarget.DepthStencilTarget)
{
OutRTInfo.DepthStencilResolveRenderTarget = OutRTInfo.DepthStencilRenderTarget;
OutRTInfo.DepthStencilResolveRenderTarget.Texture = DepthStencilRenderTarget.ResolveTarget;
}
OutRTInfo.ShadingRateTexture = ShadingRateTexture;
OutRTInfo.ShadingRateTextureCombiner = ShadingRateTextureCombiner;
OutRTInfo.MultiViewCount = MultiViewCount;
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
void FRHIRenderPassInfo::Validate() const
{
int32 NumSamples = -1; // -1 means nothing found yet
int32 ColorIndex = 0;
for (; ColorIndex < MaxSimultaneousRenderTargets; ++ColorIndex)
{
const FColorEntry& Entry = ColorRenderTargets[ColorIndex];
if (Entry.RenderTarget)
{
// Ensure NumSamples matches amongst all color RTs
if (NumSamples == -1)
{
NumSamples = Entry.RenderTarget->GetNumSamples();
}
else
{
// CustomResolveSubpass can have targets with a different NumSamples
ensureMsgf(Entry.RenderTarget->GetNumSamples() == NumSamples || SubpassHint == ESubpassHint::CustomResolveSubpass, TEXT("RenderTarget have inconsistent NumSamples: first %d, then %d"), NumSamples, Entry.RenderTarget->GetNumSamples());
}
ERenderTargetStoreAction Store = GetStoreAction(Entry.Action);
// Don't try to resolve a non-msaa
ensure(Store != ERenderTargetStoreAction::EMultisampleResolve || Entry.RenderTarget->GetNumSamples() > 1);
// Don't resolve to null
ensure(Store != ERenderTargetStoreAction::EMultisampleResolve || Entry.ResolveTarget);
if (Entry.ResolveTarget)
{
//ensure(Store == ERenderTargetStoreAction::EMultisampleResolve);
}
}
else
{
break;
}
}
int32 NumColorRenderTargets = ColorIndex;
for (; ColorIndex < MaxSimultaneousRenderTargets; ++ColorIndex)
{
// Gap in the sequence of valid render targets (ie RT0, null, RT2, ...)
ensureMsgf(!ColorRenderTargets[ColorIndex].RenderTarget, TEXT("Missing color render target on slot %d"), ColorIndex - 1);
}
if (DepthStencilRenderTarget.DepthStencilTarget)
{
// Ensure NumSamples matches with color RT
if (NumSamples != -1)
{
ensure(DepthStencilRenderTarget.DepthStencilTarget->GetNumSamples() == NumSamples);
}
ERenderTargetStoreAction DepthStore = GetStoreAction(GetDepthActions(DepthStencilRenderTarget.Action));
ERenderTargetStoreAction StencilStore = GetStoreAction(GetStencilActions(DepthStencilRenderTarget.Action));
bool bIsMSAAResolve = (DepthStore == ERenderTargetStoreAction::EMultisampleResolve) || (StencilStore == ERenderTargetStoreAction::EMultisampleResolve);
// Don't try to resolve a non-msaa
ensure(!bIsMSAAResolve || DepthStencilRenderTarget.DepthStencilTarget->GetNumSamples() > 1);
// Don't resolve to null
//ensure(DepthStencilRenderTarget.ResolveTarget || DepthStore != ERenderTargetStoreAction::EStore);
// Don't write to depth if read-only
//ensure(DepthStencilRenderTarget.ExclusiveDepthStencil.IsDepthWrite() || DepthStore != ERenderTargetStoreAction::EStore);
// This is not true for stencil. VK and Metal specify that the DontCare store action MAY leave the attachment in an undefined state.
/*ensure(DepthStencilRenderTarget.ExclusiveDepthStencil.IsStencilWrite() || StencilStore != ERenderTargetStoreAction::EStore);*/
// If we have a depthstencil target we MUST Store it or it will be undefined after rendering.
if (DepthStencilRenderTarget.DepthStencilTarget->GetFormat() != PF_D24)
{
// If this is DepthStencil we must store it out unless we are absolutely sure it will never be used again.
// it is valid to use a depthbuffer for performance and not need the results later.
//ensure(StencilStore == ERenderTargetStoreAction::EStore);
}
if (DepthStencilRenderTarget.ExclusiveDepthStencil.IsDepthWrite())
{
// this check is incorrect for mobile, depth/stencil is intermediate and we don't want to store it to main memory
//ensure(DepthStore == ERenderTargetStoreAction::EStore);
}
if (DepthStencilRenderTarget.ExclusiveDepthStencil.IsStencilWrite())
{
// this check is incorrect for mobile, depth/stencil is intermediate and we don't want to store it to main memory
//ensure(StencilStore == ERenderTargetStoreAction::EStore);
}
if (SubpassHint == ESubpassHint::DepthReadSubpass || SubpassHint == ESubpassHint::CustomResolveSubpass)
{
// for depth read sub-pass
// 1. render pass must have depth target
// 2. depth target must support InputAttachement
ensure(EnumHasAnyFlags(DepthStencilRenderTarget.DepthStencilTarget->GetFlags(), TexCreate_InputAttachmentRead));
}
if (DepthStencilRenderTarget.ResolveTarget && DepthStencilRenderTarget.ResolveTarget != DepthStencilRenderTarget.DepthStencilTarget)
{
// For depth resolve
// 1. RHI must support depth stencil resolve
// 2. Must be using MSAA resolve
// 3. Resolve target sample count must be 1
// 4. Resolve target format must be the same as the MSAA target format
ensureMsgf(GRHISupportsDepthStencilResolve, TEXT("Attempted to resolve depth/stencil target but feature is not supported."));
ensureMsgf(bIsMSAAResolve, TEXT("Depth/stencil resolve target is bound but resolve was not requested."));
ensureMsgf(DepthStencilRenderTarget.ResolveTarget->GetNumSamples() == 1, TEXT("Depth/stencil resolve targets must have a sample count of 1."));
ensureMsgf(DepthStencilRenderTarget.ResolveTarget->GetFormat() == DepthStencilRenderTarget.DepthStencilTarget->GetFormat(),
TEXT("Depth/stencil resolve targets must have the same format as the MSAA target."));
}
}
else
{
ensure(DepthStencilRenderTarget.Action == EDepthStencilTargetActions::DontLoad_DontStore);
ensure(DepthStencilRenderTarget.ExclusiveDepthStencil == FExclusiveDepthStencil::DepthNop_StencilNop);
ensure(SubpassHint != ESubpassHint::DepthReadSubpass);
ensure(SubpassHint != ESubpassHint::CustomResolveSubpass);
}
}
#endif
#define ValidateResourceDesc(expr, format, ...) \
if (bFatal) \
{ \
checkf(expr, format, ##__VA_ARGS__); \
} \
else if (!(expr)) \
{ \
return false;\
} \
// static
bool FRHITextureDesc::Validate(const FRHITextureCreateInfo& Desc, const TCHAR* Name, bool bFatal)
{
// Validate texture's pixel format.
{
ValidateResourceDesc(Desc.Format != PF_Unknown, TEXT("Illegal to create texture %s with an invalid pixel format."), Name);
ValidateResourceDesc(Desc.Format < PF_MAX, TEXT("Illegal to create texture %s with an invalid pixel format."), Name);
ValidateResourceDesc(GPixelFormats[Desc.Format].Supported,
TEXT("Failed to create texture %s with pixel format %s because it is not supported."),
Name,
GPixelFormats[Desc.Format].Name);
}
// Validate texture's extent.
{
int32 MaxDimension = (Desc.Dimension == ETextureDimension::TextureCube || Desc.Dimension == ETextureDimension::TextureCubeArray) ? GMaxCubeTextureDimensions : GMaxTextureDimensions;
ValidateResourceDesc(Desc.Extent.X > 0, TEXT("Texture %s's Extent.X=%d is invalid."), Name, Desc.Extent.X);
ValidateResourceDesc(Desc.Extent.X <= MaxDimension, TEXT("Texture %s's Extent.X=%d is too large."), Name, Desc.Extent.X);
ValidateResourceDesc(Desc.Extent.Y > 0, TEXT("Texture %s's Extent.Y=%d is invalid."), Name, Desc.Extent.Y);
ValidateResourceDesc(Desc.Extent.Y <= MaxDimension, TEXT("Texture %s's Extent.Y=%d is too large."), Name, Desc.Extent.Y);
}
// Validate texture's depth
if (Desc.Dimension == ETextureDimension::Texture3D)
{
ValidateResourceDesc(Desc.Depth > 0, TEXT("Texture %s's Depth=%d is invalid."), Name, int32(Desc.Depth));
ValidateResourceDesc(Desc.Depth <= GMaxTextureDimensions, TEXT("Texture %s's Extent.Depth=%d is too large."), Name, Desc.Depth);
}
else
{
ValidateResourceDesc(Desc.Depth == 1, TEXT("Texture %s's Depth=%d is invalid for Dimension=%s."), Name, int32(Desc.Depth), GetTextureDimensionString(Desc.Dimension));
}
// Validate texture's array size
if (Desc.Dimension == ETextureDimension::Texture2DArray || Desc.Dimension == ETextureDimension::TextureCubeArray)
{
ValidateResourceDesc(Desc.ArraySize > 0, TEXT("Texture %s's ArraySize=%d is invalid."), Name, Desc.ArraySize);
ValidateResourceDesc(Desc.ArraySize <= GMaxTextureArrayLayers, TEXT("Texture %s's Extent.ArraySize=%d is too large."), Name, int32(Desc.ArraySize));
}
else
{
ValidateResourceDesc(Desc.ArraySize == 1, TEXT("Texture %s's ArraySize=%d is invalid for Dimension=%s."), Name, Desc.ArraySize, GetTextureDimensionString(Desc.Dimension));
}
// Validate texture's samples count.
if (Desc.Dimension == ETextureDimension::Texture2D || Desc.Dimension == ETextureDimension::Texture2DArray)
{
ValidateResourceDesc(Desc.NumSamples > 0, TEXT("Texture %s's NumSamples=%d is invalid."), Name, Desc.NumSamples);
}
else
{
ValidateResourceDesc(Desc.NumSamples == 1, TEXT("Texture %s's NumSamples=%d is invalid for Dimension=%s."), Name, Desc.NumSamples, GetTextureDimensionString(Desc.Dimension));
}
// Validate texture's mips.
if (Desc.IsMultisample())
{
ValidateResourceDesc(Desc.NumMips == 1, TEXT("MSAA Texture %s's can only have one mip."), Name);
}
else
{
ValidateResourceDesc(Desc.NumMips > 0, TEXT("Texture %s's NumMips=%d is invalid."), Name, Desc.NumMips);
ValidateResourceDesc(Desc.NumMips <= GMaxTextureMipCount, TEXT("Texture %s's NumMips=%d is too large."), Name, Desc.NumMips);
}
// Validate reserved resource restrictions
if (EnumHasAnyFlags(Desc.Flags, TexCreate_ReservedResource))
{
ValidateResourceDesc(GRHIGlobals.ReservedResources.Supported,
TEXT("Reserved Texture %s's can't be created because current RHI does not support reserved resources."),
Name);
if (Desc.IsTexture3D())
{
ValidateResourceDesc(GRHIGlobals.ReservedResources.SupportsVolumeTextures,
TEXT("Reserved Texture %s's can't be created because current RHI does not support reserved volume textures."),
Name);
}
else
{
ValidateResourceDesc(
Desc.Dimension == ETextureDimension::Texture2D ||
Desc.Dimension == ETextureDimension::Texture2DArray,
TEXT("Reserved Texture %s's Desc.Dimension=%s is invalid. Expected Texture2D, Texture2DArray or Texture3D."),
Name, GetTextureDimensionString(Desc.Dimension));
}
if (Desc.Dimension == ETextureDimension::Texture2DArray)
{
const uint32 MipShift = FMath::Max<uint32>(1u, Desc.NumMips) - 1;
const FIntPoint SmallestMipExtent = Desc.Extent / (1 << MipShift);
ValidateResourceDesc(SmallestMipExtent.X >= GRHIGlobals.ReservedResources.TextureArrayMinimumMipDimension,
TEXT("Reserved Texture array %s's SmallestMipExtent.X=%d is invalid. It is required to be be no less than %d."),
Name, SmallestMipExtent.X, GRHIGlobals.ReservedResources.TextureArrayMinimumMipDimension);
ValidateResourceDesc(SmallestMipExtent.Y >= GRHIGlobals.ReservedResources.TextureArrayMinimumMipDimension,
TEXT("Reserved Texture array %s's SmallestMipExtent.Y=%d is invalid. It is required to be be no less than %d."),
Name, SmallestMipExtent.Y, GRHIGlobals.ReservedResources.TextureArrayMinimumMipDimension);
}
}
return true;
}
// static
bool FRHITextureSRVCreateInfo::Validate(const FRHITextureDesc& TextureDesc, const FRHITextureSRVCreateInfo& TextureSRVDesc, const TCHAR* TextureName, bool bFatal)
{
if (TextureName == nullptr)
{
TextureName = TEXT("UnnamedTexture");
}
ValidateResourceDesc(TextureDesc.Flags & TexCreate_ShaderResource,
TEXT("Attempted to create SRV from texture %s which was not created with TexCreate_ShaderResource"),
TextureName);
// Validate the pixel format if overridden by the SRV's descriptor.
if (TextureSRVDesc.Format == PF_X24_G8)
{
// PF_X24_G8 is a bit of mess in the RHI, used to read the stencil, but have varying BlockBytes.
ValidateResourceDesc(TextureDesc.Format == PF_DepthStencil,
TEXT("PF_X24_G8 is only to read stencil from a PF_DepthStencil texture"));
}
else if (TextureSRVDesc.Format != PF_Unknown)
{
ValidateResourceDesc(TextureSRVDesc.Format < PF_MAX,
TEXT("Illegal to create SRV for texture %s with invalid FPooledRenderTargetDesc::Format."),
TextureName);
ValidateResourceDesc(GPixelFormats[TextureSRVDesc.Format].Supported,
TEXT("Failed to create SRV for texture %s with pixel format %s because it is not supported."),
TextureName, GPixelFormats[TextureSRVDesc.Format].Name);
EPixelFormat ResourcePixelFormat = TextureDesc.Format;
ValidateResourceDesc(
GPixelFormats[TextureSRVDesc.Format].BlockBytes == GPixelFormats[ResourcePixelFormat].BlockBytes &&
GPixelFormats[TextureSRVDesc.Format].BlockSizeX == GPixelFormats[ResourcePixelFormat].BlockSizeX &&
GPixelFormats[TextureSRVDesc.Format].BlockSizeY == GPixelFormats[ResourcePixelFormat].BlockSizeY &&
GPixelFormats[TextureSRVDesc.Format].BlockSizeZ == GPixelFormats[ResourcePixelFormat].BlockSizeZ,
TEXT("Failed to create SRV for texture %s with pixel format %s because it does not match the byte size of the texture's pixel format %s."),
TextureName, GPixelFormats[TextureSRVDesc.Format].Name, GPixelFormats[ResourcePixelFormat].Name);
}
ValidateResourceDesc((TextureSRVDesc.MipLevel + TextureSRVDesc.NumMipLevels) <= TextureDesc.NumMips,
TEXT("Failed to create SRV at mips %d-%d: the texture %s has only %d mip levels."),
TextureSRVDesc.MipLevel, (TextureSRVDesc.MipLevel + TextureSRVDesc.NumMipLevels), TextureName, TextureDesc.NumMips);
// Validate the array slices
if (TextureDesc.IsTextureCube() && TextureSRVDesc.DimensionOverride == ETextureDimension::Texture2DArray)
{
// Either TextureCube or TextureCubeArray, compute array size appropriately
int32 CubeArraySize = TextureDesc.Dimension == ETextureDimension::TextureCube ? ECubeFace::CubeFace_MAX : ECubeFace::CubeFace_MAX * TextureDesc.ArraySize;
ValidateResourceDesc((TextureSRVDesc.FirstArraySlice + TextureSRVDesc.NumArraySlices) <= CubeArraySize,
TEXT("Failed to create SRV at array slices %d-%d: the cube map texture %s has only %d slices."),
TextureSRVDesc.FirstArraySlice,
(TextureSRVDesc.FirstArraySlice + TextureSRVDesc.NumArraySlices),
TextureName,
CubeArraySize);
}
else if (TextureDesc.IsTextureArray())
{
ValidateResourceDesc((TextureSRVDesc.FirstArraySlice + TextureSRVDesc.NumArraySlices) <= TextureDesc.ArraySize,
TEXT("Failed to create SRV at array slices %d-%d: the texture array %s has only %d slices."),
TextureSRVDesc.FirstArraySlice,
(TextureSRVDesc.FirstArraySlice + TextureSRVDesc.NumArraySlices),
TextureName,
TextureDesc.ArraySize);
}
else
{
ValidateResourceDesc(TextureSRVDesc.FirstArraySlice == 0,
TEXT("Failed to create SRV with FirstArraySlice=%d: the texture %s is not a texture array."),
TextureSRVDesc.FirstArraySlice, TextureName);
ValidateResourceDesc(TextureSRVDesc.NumArraySlices == 0,
TEXT("Failed to create SRV with NumArraySlices=%d: the texture %s is not a texture array."),
TextureSRVDesc.NumArraySlices, TextureName);
}
ValidateResourceDesc(TextureSRVDesc.MetaData != ERHITextureMetaDataAccess::FMask || GRHISupportsExplicitFMask,
TEXT("Failed to create FMask SRV for texture %s because the current RHI doesn't support it. Be sure to gate the call with GRHISupportsExplicitFMask."),
TextureName);
ValidateResourceDesc(TextureSRVDesc.MetaData != ERHITextureMetaDataAccess::HTile || GRHISupportsExplicitHTile,
TEXT("Failed to create HTile SRV for texture %s because the current RHI doesn't support it. Be sure to gate the call with GRHISupportsExplicitHTile."),
TextureName);
return true;
}
#undef ValidateResourceDesc
uint64 FRHITextureDesc::CalcMemorySizeEstimate(uint32 FirstMipIndex, uint32 LastMipIndex) const
{
#if DO_CHECK
Validate(*this, TEXT("CalcMemorySizeEstimate"), /* bFatal = */true);
#endif
check(FirstMipIndex < NumMips && FirstMipIndex <= LastMipIndex && LastMipIndex < NumMips);
uint64 MemorySize = 0;
for (uint32 MipIndex = FirstMipIndex; MipIndex <= LastMipIndex; ++MipIndex)
{
FIntVector MipSizeInBlocks = FIntVector(
FMath::DivideAndRoundUp(FMath::Max(Extent.X >> MipIndex, 1), GPixelFormats[Format].BlockSizeX),
FMath::DivideAndRoundUp(FMath::Max(Extent.Y >> MipIndex, 1), GPixelFormats[Format].BlockSizeY),
FMath::DivideAndRoundUp(FMath::Max(Depth >> MipIndex, 1), GPixelFormats[Format].BlockSizeZ)
);
uint32 NumBlocksInMip = MipSizeInBlocks.X * MipSizeInBlocks.Y * MipSizeInBlocks.Z;
MemorySize += NumBlocksInMip * GPixelFormats[Format].BlockBytes;
}
MemorySize *= ArraySize;
MemorySize *= NumSamples;
if (IsTextureCube())
{
MemorySize *= 6;
}
return MemorySize;
}
static FRHIPanicEvent RHIPanicEvent;
FRHIPanicEvent& RHIGetPanicDelegate()
{
return RHIPanicEvent;
}
int32 CalculateMSAASampleArrayIndex(int32 NumSamples, int32 SampleIndex)
{
check(NumSamples > 0 && NumSamples <= 16);
check(FMath::IsPowerOfTwo(NumSamples));
check(SampleIndex < NumSamples);
return NumSamples - 1 + SampleIndex;
}
void RHIInitDefaultPixelFormatCapabilities()
{
for (FPixelFormatInfo& Info : GPixelFormats)
{
if (Info.Supported)
{
const EPixelFormat PixelFormat = Info.UnrealFormat;
if (IsBlockCompressedFormat(PixelFormat))
{
// Block compressed formats should have limited capabilities
EnumAddFlags(Info.Capabilities, EPixelFormatCapabilities::AnyTexture | EPixelFormatCapabilities::TextureMipmaps | EPixelFormatCapabilities::TextureLoad | EPixelFormatCapabilities::TextureSample | EPixelFormatCapabilities::TextureGather | EPixelFormatCapabilities::TextureFilterable);
}
else
{
EnumAddFlags(Info.Capabilities, EPixelFormatCapabilities::AllTextureFlags | EPixelFormatCapabilities::AllBufferFlags | EPixelFormatCapabilities::UAV);
if (!IsDepthOrStencilFormat(PixelFormat))
{
EnumRemoveFlags(Info.Capabilities, EPixelFormatCapabilities::DepthStencil);
}
}
}
}
}
//
// CalculateImageBytes
//
SIZE_T CalculateImageBytes(uint32 SizeX,uint32 SizeY,uint32 SizeZ,uint8 Format)
{
if ( Format == PF_A1 )
{
// The number of bytes needed to store all 1 bit pixels in a line is the width of the image divided by the number of bits in a byte
uint32 BytesPerLine = (SizeX + 7) / 8;
// The number of actual bytes in a 1 bit image is the bytes per line of pixels times the number of lines
return sizeof(uint8) * BytesPerLine * SizeY;
}
else if( SizeZ > 0 )
{
return GPixelFormats[Format].Get3DImageSizeInBytes(SizeX, SizeY, SizeZ);
}
else
{
return GPixelFormats[Format].Get2DImageSizeInBytes(SizeX, SizeY);
}
}
FRHIShaderResourceView* FRHITextureViewCache::GetOrCreateSRV(FRHICommandListBase& RHICmdList, FRHITexture* Texture, const FRHITextureSRVCreateInfo& SRVCreateInfo)
{
for (const auto& KeyValue : SRVs)
{
if (KeyValue.Key == SRVCreateInfo)
{
return KeyValue.Value.GetReference();
}
}
check(Texture);
ETextureDimension Dimension = Texture->GetDesc().Dimension;
if(SRVCreateInfo.DimensionOverride.IsSet())
{
Dimension = *SRVCreateInfo.DimensionOverride;
}
FShaderResourceViewRHIRef RHIShaderResourceView = RHICmdList.CreateShaderResourceView(Texture, FRHIViewDesc::CreateTextureSRV()
.SetDimension (Dimension)
.SetFormat (SRVCreateInfo.Format)
.SetMipRange (SRVCreateInfo.MipLevel, SRVCreateInfo.NumMipLevels)
.SetDisableSRGB (SRVCreateInfo.SRGBOverride == SRGBO_ForceDisable)
.SetArrayRange (SRVCreateInfo.FirstArraySlice, SRVCreateInfo.NumArraySlices)
.SetPlane (SRVCreateInfo.MetaData)
);
check(RHIShaderResourceView);
FRHIShaderResourceView* View = RHIShaderResourceView.GetReference();
SRVs.Emplace(SRVCreateInfo, MoveTemp(RHIShaderResourceView));
return View;
}
FRHIUnorderedAccessView* FRHITextureViewCache::GetOrCreateUAV(FRHICommandListBase& RHICmdList, FRHITexture* Texture, const FRHITextureUAVCreateInfo& UAVCreateInfo)
{
for (const auto& KeyValue : UAVs)
{
if (KeyValue.Key == UAVCreateInfo)
{
return KeyValue.Value.GetReference();
}
}
check(Texture);
ETextureDimension Dimension = Texture->GetDesc().Dimension;
if(UAVCreateInfo.DimensionOverride.IsSet())
{
Dimension = *UAVCreateInfo.DimensionOverride;
}
FUnorderedAccessViewRHIRef RHIUnorderedAccessView = RHICmdList.CreateUnorderedAccessView(Texture, FRHIViewDesc::CreateTextureUAV()
.SetDimension (Dimension)
.SetFormat (UAVCreateInfo.Format)
.SetMipLevel (UAVCreateInfo.MipLevel)
.SetArrayRange(UAVCreateInfo.FirstArraySlice, UAVCreateInfo.NumArraySlices)
.SetPlane (UAVCreateInfo.MetaData)
);
check(RHIUnorderedAccessView);
FRHIUnorderedAccessView* View = RHIUnorderedAccessView.GetReference();
UAVs.Emplace(UAVCreateInfo, MoveTemp(RHIUnorderedAccessView));
return View;
}
FRHIShaderResourceView* FRHIBufferViewCache::GetOrCreateSRV(FRHICommandListBase& RHICmdList, FRHIBuffer* Buffer, const FRHIBufferSRVCreateInfo& SRVCreateInfo)
{
// Bypass the cache when using the ray tracing scene, as there is no guarantee the scene will not be recreated, which would grow the cache unbounded.
if (!SRVCreateInfo.RayTracingScene)
{
for (const auto& KeyValue : SRVs)
{
if (KeyValue.Key == SRVCreateInfo)
{
return KeyValue.Value.GetReference();
}
}
}
auto CreateDesc = FRHIViewDesc::CreateBufferSRV();
if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_ByteAddressBuffer))
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::Raw);
}
else if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_StructuredBuffer))
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::Structured);
}
else if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_AccelerationStructure))
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::AccelerationStructure);
CreateDesc.SetRayTracingScene(SRVCreateInfo.RayTracingScene);
}
else
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::Typed);
CreateDesc.SetFormat(SRVCreateInfo.Format);
}
CreateDesc.SetOffsetInBytes(SRVCreateInfo.StartOffsetBytes);
if (SRVCreateInfo.NumElements != UINT32_MAX)
{
CreateDesc.SetNumElements(SRVCreateInfo.NumElements);
}
FShaderResourceViewRHIRef RHIShaderResourceView = RHICmdList.CreateShaderResourceView(Buffer, CreateDesc);
FRHIShaderResourceView* View = RHIShaderResourceView.GetReference();
if (!SRVCreateInfo.RayTracingScene)
{
SRVs.Emplace(SRVCreateInfo, MoveTemp(RHIShaderResourceView));
}
return View;
}
FRHIUnorderedAccessView* FRHIBufferViewCache::GetOrCreateUAV(FRHICommandListBase& RHICmdList, FRHIBuffer* Buffer, const FRHIBufferUAVCreateInfo& UAVCreateInfo)
{
for (const auto& KeyValue : UAVs)
{
if (KeyValue.Key == UAVCreateInfo)
{
return KeyValue.Value.GetReference();
}
}
auto CreateDesc = FRHIViewDesc::CreateBufferUAV();
CreateDesc.SetAtomicCounter(UAVCreateInfo.bSupportsAtomicCounter);
CreateDesc.SetAppendBuffer(UAVCreateInfo.bSupportsAppendBuffer);
if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_ByteAddressBuffer))
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::Raw);
}
else if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_StructuredBuffer))
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::Structured);
}
else if (EnumHasAnyFlags(Buffer->GetUsage(), BUF_AccelerationStructure))
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::AccelerationStructure);
}
else
{
CreateDesc.SetType(FRHIViewDesc::EBufferType::Typed);
CreateDesc.SetFormat(UAVCreateInfo.Format);
}
FUnorderedAccessViewRHIRef RHIUnorderedAccessView = RHICmdList.CreateUnorderedAccessView(Buffer, CreateDesc);
FRHIUnorderedAccessView* View = RHIUnorderedAccessView.GetReference();
UAVs.Emplace(UAVCreateInfo, MoveTemp(RHIUnorderedAccessView));
return View;
}
#if RHI_USE_RESOURCE_DEBUG_NAME
void FRHITextureViewCache::SetDebugName(FRHICommandListBase& RHICmdList, const TCHAR* DebugName)
{
for (const auto& KeyValue : UAVs)
{
RHICmdList.BindDebugLabelName(KeyValue.Value, DebugName);
}
}
void FRHIBufferViewCache::SetDebugName(FRHICommandListBase& RHICmdList, const TCHAR* DebugName)
{
for (const auto& KeyValue : UAVs)
{
RHICmdList.BindDebugLabelName(KeyValue.Value, DebugName);
}
}
#endif
void FRHITransientTexture::BindDebugLabelName(FRHICommandListBase& RHICmdList)
{
ViewCache.SetDebugName(RHICmdList, GetName());
#if RHI_USE_RESOURCE_DEBUG_NAME
RHICmdList.BindDebugLabelName(GetRHI(), GetName());
#endif
}
void FRHITransientBuffer::BindDebugLabelName(FRHICommandListBase& RHICmdList)
{
ViewCache.SetDebugName(RHICmdList, GetName());
// TODO: Add method to rename a buffer.
}
FDebugName::FDebugName()
: Name()
, Number(NAME_NO_NUMBER_INTERNAL)
{
}
FDebugName::FDebugName(FName InName)
: Name(InName)
, Number(NAME_NO_NUMBER_INTERNAL)
{
}
FDebugName::FDebugName(FName InName, int32 InNumber)
: Name(InName)
, Number(InNumber)
{
}
FDebugName& FDebugName::operator=(FName Other)
{
Name = Other;
Number = NAME_NO_NUMBER_INTERNAL;
return *this;
}
FString FDebugName::ToString() const
{
FString Out;
Name.AppendString(Out);
if (Number != NAME_NO_NUMBER_INTERNAL)
{
Out.Appendf(TEXT("_%u"), Number);
}
return Out;
}
uint32 FDebugName::ToStringInternal(TCHAR* Out, const uint32 OutSize) const
{
check(OutSize > 0);
const uint32 OutLen = OutSize - 1;
uint32 NameLen = Name.ToStringTruncate(Out, OutSize);
if (Number != NAME_NO_NUMBER_INTERNAL && OutLen > NameLen)
{
int32 SuffixLen = FCString::Snprintf(Out + NameLen, OutSize - NameLen, TEXT("_%d"), Number);
if (SuffixLen < 0)
{
// Suffix was truncated.
return OutLen;
}
else
{
// Suffix was fully written
return NameLen + SuffixLen;
}
}
else
{
// No suffix, or no room to add it
return FMath::Min<uint32>(OutLen, NameLen);
}
}
void FDebugName::AppendString(FStringBuilderBase& Builder) const
{
Name.AppendString(Builder);
if (Number != NAME_NO_NUMBER_INTERNAL)
{
Builder << '_' << Number;
}
}
namespace UE::RHI
{
static TAutoConsoleVariable<int32> CVarGPUCrashDebugging(
TEXT("r.GPUCrashDebugging"),
0,
TEXT("Enable vendor specific GPU crash analysis tools"),
ECVF_ReadOnly
);
RHI_API bool UseGPUCrashDebugging()
{
static const bool bNoGpuCrashDebugging = FParse::Param(FCommandLine::Get(), TEXT("nogpucrashdebugging"));
static const bool bGpuCrashDebugging = FParse::Param(FCommandLine::Get(), TEXT("gpucrashdebugging"));
// Command line takes precedence
if (bNoGpuCrashDebugging)
{
return false;
}
else if (bGpuCrashDebugging)
{
return true;
}
else
{
return CVarGPUCrashDebugging.GetValueOnAnyThread() != 0;
}
}
RHI_API bool ShouldEnableGPUCrashFeature(IConsoleVariable& CVar, TCHAR const* CommandLineSwitch)
{
static const bool bNoGpuCrashDebugging = FParse::Param(FCommandLine::Get(), TEXT("nogpucrashdebugging"));
static const bool bGpuCrashDebugging = FParse::Param(FCommandLine::Get(), TEXT("gpucrashdebugging"));
bool bEnabled;
if (bNoGpuCrashDebugging)
{
// Command line switch is forcing everything off
bEnabled = false;
}
else if (bGpuCrashDebugging)
{
// Command line switch is forcing everything on
bEnabled = true;
}
else if (CVarGPUCrashDebugging->GetInt() > 0)
{
// Otherwise, switch everything on when opt-ed in via the r.GPUCrashDebugging cvar.
bEnabled = true;
}
else
{
// If none of the above apply, check the individual feature cvar.
bEnabled = CVar.GetInt() > 0;
}
// Allow additional command line switches to force on/off the feature via "-feature=1" / "-feature=0", or simply "-feature".
int32 Value = 0;
if (FParse::Value(FCommandLine::Get(), *FString::Printf(TEXT("%s="), CommandLineSwitch), Value))
{
bEnabled = Value > 0;
}
else if (FParse::Param(FCommandLine::Get(), CommandLineSwitch))
{
bEnabled = true;
}
return bEnabled;
}
static TAutoConsoleVariable<int32> CVarGPUCrashDebuggingBreadcrumbs(
TEXT("r.GPUCrashDebugging.Breadcrumbs"),
1,
TEXT("Enable RHI breadcrumbs, a vendor-agnostic method for determining which passes were active when a GPU crash occurs"),
ECVF_ReadOnly
);
RHI_API bool UseGPUCrashBreadcrumbs()
{
static bool bEnabled = ShouldEnableGPUCrashFeature(*CVarGPUCrashDebuggingBreadcrumbs, TEXT("gpubreadcrumbs"));
return bEnabled;
}
RHI_API void CopySharedMips(FRHICommandList& RHICmdList, FRHITexture* SrcTexture, FRHITexture* DstTexture)
{
FRHITextureDesc const& Desc = DstTexture->GetNumMips() < SrcTexture->GetNumMips()
? DstTexture->GetDesc()
: SrcTexture->GetDesc();
FRHICopyTextureInfo CopyInfo;
CopyInfo.Size.X = Desc.Extent.X;
CopyInfo.Size.Y = Desc.Extent.Y;
CopyInfo.Size.Z = Desc.Depth;
CopyInfo.NumSlices = Desc.ArraySize;
CopyInfo.NumMips = Desc.NumMips;
CopyInfo.SourceMipIndex = SrcTexture->GetNumMips() - CopyInfo.NumMips;
CopyInfo.DestMipIndex = DstTexture->GetNumMips() - CopyInfo.NumMips;
RHICmdList.CopyTexture(SrcTexture, DstTexture, CopyInfo);
}
RHI_API void CopySharedMips_AssumeSRVMaskState(FRHICommandList& RHICmdList, FRHITexture* SrcTexture, FRHITexture* DstTexture)
{
// Transition to copy source and dest
{
FRHITransitionInfo TransitionsBefore[] =
{
FRHITransitionInfo(SrcTexture, ERHIAccess::SRVMask, ERHIAccess::CopySrc),
FRHITransitionInfo(DstTexture, ERHIAccess::SRVMask, ERHIAccess::CopyDest)
};
RHICmdList.Transition(MakeArrayView(TransitionsBefore, UE_ARRAY_COUNT(TransitionsBefore)));
}
CopySharedMips(RHICmdList, SrcTexture, DstTexture);
// Transition to SRV
{
FRHITransitionInfo TransitionsAfter[] =
{
FRHITransitionInfo(SrcTexture, ERHIAccess::CopySrc, ERHIAccess::SRVMask),
FRHITransitionInfo(DstTexture, ERHIAccess::CopyDest, ERHIAccess::SRVMask)
};
RHICmdList.Transition(MakeArrayView(TransitionsAfter, UE_ARRAY_COUNT(TransitionsAfter)));
}
}
} //! UE::RHI