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

828 lines
24 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
RHIUtilities.cpp:
=============================================================================*/
#include "RHIUtilities.h"
#include "Async/TaskGraphInterfaces.h"
#include "HAL/PlatformStackWalk.h"
#include "RHI.h"
#include "GenericPlatform/GenericPlatformFramePacer.h"
#include "HAL/Runnable.h"
#include "HAL/RunnableThread.h"
#include "HAL/PlatformFramePacer.h"
#include "Misc/CommandLine.h"
#include "RHIAccess.h"
#include "RHIFwd.h"
#include "RHIStrings.h"
#include "Tasks/Task.h"
#include "ProfilingDebugging/MiscTrace.h"
#define USE_FRAME_OFFSET_THREAD 1
#if !UE_BUILD_SHIPPING && PLATFORM_SUPPORTS_FLIP_TRACKING
bool GVsyncInformationInsights = false;
static FAutoConsoleVariableRef CVarVsyncInformationInsights(
TEXT("r.VsyncInformationInsights"),
GVsyncInformationInsights,
TEXT("Whether to show Vsync and Input events in UnrealInsights"));
#endif
TAutoConsoleVariable<FString> FDumpTransitionsHelper::CVarDumpTransitionsForResource(
TEXT("r.DumpTransitionsForResource"),
TEXT(""),
TEXT("Prints callstack when the given resource is transitioned. Only implemented for DX11 at the moment.")
TEXT("Name of the resource to dump"),
ECVF_Default);
FName FDumpTransitionsHelper::DumpTransitionForResource = NAME_None;
void FDumpTransitionsHelper::DumpTransitionForResourceHandler()
{
const FString NewValue = CVarDumpTransitionsForResource.GetValueOnGameThread();
DumpTransitionForResource = FName(*NewValue);
}
void FDumpTransitionsHelper::DumpResourceTransition(const FName& ResourceName, const ERHIAccess TransitionType)
{
const FName ResourceDumpName = FDumpTransitionsHelper::DumpTransitionForResource;
if ((ResourceDumpName != NAME_None) && (ResourceDumpName == ResourceName))
{
const uint32 DumpCallstackSize = 2047;
ANSICHAR DumpCallstack[DumpCallstackSize] = { 0 };
FPlatformStackWalk::StackWalkAndDump(DumpCallstack, DumpCallstackSize, 2);
UE_LOG(LogRHI, Log, TEXT("%s transition to: %s"), *ResourceDumpName.ToString(), *GetRHIAccessName(TransitionType));
UE_LOG(LogRHI, Log, TEXT("%s"), ANSI_TO_TCHAR(DumpCallstack));
}
}
FAutoConsoleVariableSink FDumpTransitionsHelper::CVarDumpTransitionsForResourceSink(FConsoleCommandDelegate::CreateStatic(&FDumpTransitionsHelper::DumpTransitionForResourceHandler));
void SetDepthBoundsTest(FRHICommandList& RHICmdList, float WorldSpaceDepthNear, float WorldSpaceDepthFar, const FMatrix& ProjectionMatrix)
{
if (GSupportsDepthBoundsTest)
{
FVector4 Near = ProjectionMatrix.TransformFVector4(FVector4(0, 0, WorldSpaceDepthNear));
FVector4 Far = ProjectionMatrix.TransformFVector4(FVector4(0, 0, WorldSpaceDepthFar));
float DepthNear = float(Near.Z / Near.W);
float DepthFar = float(Far.Z / Far.W);
DepthFar = FMath::Clamp(DepthFar, 0.0f, 1.0f);
DepthNear = FMath::Clamp(DepthNear, 0.0f, 1.0f);
if (DepthNear <= DepthFar)
{
DepthNear = 1.0f;
DepthFar = 0.0f;
}
// Note, using a reversed z depth surface
RHICmdList.SetDepthBounds(DepthFar, DepthNear);
}
}
TAutoConsoleVariable<int32> CVarRHISyncInterval(
TEXT("rhi.SyncInterval"),
1,
TEXT("Determines the frequency of VSyncs in supported RHIs.\n")
TEXT("This is in multiples of 16.66 on a 60hz display, but some platforms support higher refresh rates.\n")
TEXT("Assuming 60fps, the values correspond to:\n")
TEXT(" 0 - Unlocked (present immediately)\n")
TEXT(" 1 - Present every vblank interval\n")
TEXT(" 2 - Present every 2 vblank intervals\n")
TEXT(" 3 - etc...\n"),
ECVF_Default
);
TAutoConsoleVariable<float> CVarRHIPresentThresholdTop(
TEXT("rhi.PresentThreshold.Top"),
0.0f,
TEXT("Specifies the percentage of the screen from the top where tearing is allowed.\n")
TEXT("Only effective on supported platforms.\n")
TEXT("Range: 0.0 - 1.0\n"),
ECVF_Default
);
TAutoConsoleVariable<float> CVarRHIPresentThresholdBottom(
TEXT("rhi.PresentThreshold.Bottom"),
0.0f,
TEXT("Specifies the percentage of the screen from the bottom where tearing is allowed.\n")
TEXT("Only effective on supported platforms.\n")
TEXT("Range: 0.0 - 1.0\n"),
ECVF_Default
);
TAutoConsoleVariable<int32> CVarRHISyncAllowEarlyKick(
TEXT("rhi.SyncAllowEarlyKick"),
1,
TEXT("When 1, allows the RHI vsync thread to kick off the next frame early if we've missed the vsync."),
ECVF_Default
);
#if USE_FRAME_OFFSET_THREAD
TAutoConsoleVariable<float> CVarRHISyncSlackMS(
TEXT("rhi.SyncSlackMS"),
10,
TEXT("Increases input latency by this many milliseconds, to help performance (trade-off tunable). Gamethread will be kicked off this many milliseconds before the vsync"),
ECVF_Default
);
#endif
TAutoConsoleVariable<int32> CVarRHISyncAllowVariable(
TEXT("rhi.SyncAllowVariable"),
1,
TEXT("When 1, allows the RHI to use variable refresh rate, if supported by the output hardware."),
ECVF_Default
);
float GRHIFrameTimeMS = 0.0f;
double GLastRHITimeInSeconds = 0.0;
int32 GEnableConsole120Fps = 0;
int32 InternalEnableConsole120Fps = 0;
static void OnEnableConsole120FpsCVarRHIChanged(IConsoleVariable* Variable)
{
InternalEnableConsole120Fps &= (FPlatformMisc::GetMaxSupportedRefreshRate() >= 120);
if (InternalEnableConsole120Fps != GEnableConsole120Fps)
{
GEnableConsole120Fps = InternalEnableConsole120Fps;
// needs to update the FramePace since it updates the SyncInterval based on the RefreshRate.
FPlatformRHIFramePacer::SetFramePace(GEnableConsole120Fps ? 120 : 60);
UE_LOG(LogRHI, Log, TEXT("Console 120Fps = %d"), GEnableConsole120Fps);
}
}
static FAutoConsoleVariableRef CVarRHInableConsole120Fps(
TEXT("rhi.EnableConsole120Fps"),
InternalEnableConsole120Fps,
TEXT("Enable Console 120fps if Monitor supports it and Console is properly setup"),
FConsoleVariableDelegate::CreateStatic(&OnEnableConsole120FpsCVarRHIChanged),
ECVF_Default
);
class FRHIFrameFlipTrackingRunnable : public FRunnable
{
static FRunnableThread* Thread;
static FRHIFrameFlipTrackingRunnable Singleton;
static bool bInitialized;
static bool bRun;
FCriticalSection CS;
struct FFramePair
{
FFramePair(uint64 InPresentIndex, const UE::Tasks::FTaskEvent& InEvent)
: PresentIndex(InPresentIndex)
, Event(InEvent)
{}
~FFramePair()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SyncTrigger_Swapchain);
Event.Trigger();
}
uint64 PresentIndex;
UE::Tasks::FTaskEvent Event;
};
TArray<FFramePair> FramePairs;
FRHIFrameFlipTrackingRunnable();
virtual uint32 Run() override;
virtual void Stop() override;
public:
static void Initialize();
static void Shutdown();
static void TriggerTaskEventOnFlip(uint64 PresentIndex, UE::Tasks::FTaskEvent Event);
};
FRHIFrameFlipTrackingRunnable::FRHIFrameFlipTrackingRunnable()
{}
#if USE_FRAME_OFFSET_THREAD
struct FRHIFrameOffsetThread : public FRunnable
{
static FRunnableThread* Thread;
static FRHIFrameOffsetThread Singleton;
static bool bInitialized;
static bool bRun;
FCriticalSection CS;
FRHIFlipDetails LastFlipFrame;
static FEvent* WaitEvent;
#if !UE_BUILD_SHIPPING
struct FFrameDebugInfo
{
uint64 PresentIndex;
uint64 FrameIndex;
uint64 InputTime;
};
TArray<FFrameDebugInfo> FrameDebugInfos;
#endif
virtual uint32 Run() override
{
while (bRun)
{
FRHIFlipDetails NewFlipFrame = GDynamicRHI->RHIWaitForFlip(-1);
int32 SyncInterval = RHIGetSyncInterval();
double TargetFrameTimeInSeconds = double(SyncInterval) / double(FPlatformMisc::GetMaxRefreshRate());
double SlackInSeconds = FMath::Min(RHIGetSyncSlackMS() / 1000.0, TargetFrameTimeInSeconds); // Clamp slack sync time to at most one full frame interval
double TargetFlipTime = (NewFlipFrame.VBlankTimeInSeconds + TargetFrameTimeInSeconds) - SlackInSeconds;
double Timeout = FMath::Max(0.0, TargetFlipTime - FPlatformTime::ToSeconds64(FPlatformTime::Cycles64()));
FPlatformProcess::Sleep(Timeout);
{
FScopeLock Lock(&CS);
LastFlipFrame = NewFlipFrame;
LastFlipFrame.FlipTimeInSeconds = LastFlipFrame.FlipTimeInSeconds + TargetFrameTimeInSeconds - SlackInSeconds;
LastFlipFrame.VBlankTimeInSeconds = LastFlipFrame.VBlankTimeInSeconds + TargetFrameTimeInSeconds - SlackInSeconds;
LastFlipFrame.PresentIndex++;
#if !UE_BUILD_SHIPPING && PLATFORM_SUPPORTS_FLIP_TRACKING
for (int32 DebugInfoIndex = FrameDebugInfos.Num() - 1; DebugInfoIndex >= 0; --DebugInfoIndex)
{
auto const& DebugInfo = FrameDebugInfos[DebugInfoIndex];
if (NewFlipFrame.PresentIndex == DebugInfo.PresentIndex)
{
uint64 VBlankTimeInCycle = (uint64)(NewFlipFrame.VBlankTimeInSeconds / FPlatformTime::GetSecondsPerCycle64());
GInputLatencyTime = (VBlankTimeInCycle) - DebugInfo.InputTime;
if (GVsyncInformationInsights)
{
TRACE_BOOKMARK_CYCLES(VBlankTimeInCycle, TEXT("Vsync P:%" UINT64_FMT " F:%" UINT64_FMT " Lat:%.2f"), DebugInfo.PresentIndex, DebugInfo.FrameIndex, FPlatformTime::ToMilliseconds64(GInputLatencyTime))
}
}
if (DebugInfo.PresentIndex <= NewFlipFrame.PresentIndex)
{
FrameDebugInfos.RemoveAtSwap(DebugInfoIndex);
}
}
#endif
}
if (WaitEvent)
{
WaitEvent->Trigger();
}
}
return 0;
}
virtual void Stop() override
{
bRun = false;
GDynamicRHI->RHISignalFlipEvent();
}
public:
FRHIFrameOffsetThread()
{}
~FRHIFrameOffsetThread()
{
}
static FRHIFlipDetails WaitForFlip(double Timeout)
{
check(Singleton.WaitEvent);
if (Timeout >= 0)
{
Singleton.WaitEvent->Wait((uint32)(Timeout * 1000.0));
}
else
{
Singleton.WaitEvent->Wait();
}
FScopeLock Lock(&Singleton.CS);
return Singleton.LastFlipFrame;
}
static void Signal()
{
Singleton.WaitEvent->Trigger();
}
static void Initialize()
{
bInitialized = true;
bRun = true;
Singleton.GetOrInitializeWaitEvent();
check(Thread == nullptr);
Thread = FRunnableThread::Create(&Singleton, TEXT("RHIFrameOffsetThread"), 0, TPri_AboveNormal, FPlatformAffinity::GetRHIFrameOffsetThreadMask());
}
static void Shutdown()
{
// Some platforms call shutdown before initialize has been called, so bail out if that happens
if (!bInitialized)
{
return;
}
bInitialized = false;
if (WaitEvent)
{
FPlatformProcess::ReturnSynchEventToPool(WaitEvent);
WaitEvent = nullptr;
}
if (Thread)
{
Thread->Kill(true);
delete Thread;
Thread = nullptr;
}
}
static void SetFrameDebugInfo(uint64 PresentIndex, uint64 FrameIndex, uint64 InputTime)
{
#if !UE_BUILD_SHIPPING && PLATFORM_SUPPORTS_FLIP_TRACKING
FScopeLock Lock(&Singleton.CS);
FFrameDebugInfo DebugInfo;
DebugInfo.PresentIndex = PresentIndex;
DebugInfo.FrameIndex = FrameIndex;
DebugInfo.InputTime = InputTime;
Singleton.FrameDebugInfos.Add(DebugInfo);
if (GVsyncInformationInsights)
{
TRACE_BOOKMARK_CYCLES(DebugInfo.InputTime, TEXT("Input P:%" UINT64_FMT " F:%" UINT64_FMT), DebugInfo.PresentIndex, DebugInfo.FrameIndex)
}
#endif
}
private:
FEvent* GetOrInitializeWaitEvent()
{
// Wait event can't be initialized with the singleton, because it will crash when initialized to early
if (WaitEvent == nullptr)
{
WaitEvent = FPlatformProcess::GetSynchEventFromPool(false);
}
return WaitEvent;
}
};
FRunnableThread* FRHIFrameOffsetThread::Thread = nullptr;
FRHIFrameOffsetThread FRHIFrameOffsetThread::Singleton;
bool FRHIFrameOffsetThread::bInitialized = false;
bool FRHIFrameOffsetThread::bRun = false;
FEvent* FRHIFrameOffsetThread::WaitEvent = nullptr;
#endif // USE_FRAME_OFFSET_THREAD
uint32 FRHIFrameFlipTrackingRunnable::Run()
{
uint64 SyncFrame = 0;
double SyncTime = FPlatformTime::Seconds();
bool bForceFlipSync = true;
if ( ! FPlatformMisc::UseRenderThread() )
{
return 0;
}
while (bRun)
{
// Determine the next expected flip time, based on the previous flip time we synced to.
int32 SyncInterval = RHIGetSyncInterval();
double TargetFrameTimeInSeconds = double(SyncInterval) / double(FPlatformMisc::GetMaxRefreshRate());
double ExpectedNextFlipTimeInSeconds = SyncTime + (TargetFrameTimeInSeconds * 1.02); // Add 2% to prevent early timeout
double CurrentTimeInSeconds = FPlatformTime::Seconds();
double TimeoutInSeconds = (SyncInterval == 0 || bForceFlipSync) ? -1.0 : FMath::Max(ExpectedNextFlipTimeInSeconds - CurrentTimeInSeconds, 0.0);
#if USE_FRAME_OFFSET_THREAD
FRHIFlipDetails FlippedFrame = FRHIFrameOffsetThread::WaitForFlip(TimeoutInSeconds);
#else
FRHIFlipDetails FlippedFrame = GDynamicRHI->RHIWaitForFlip(TimeoutInSeconds);
#endif
CurrentTimeInSeconds = FPlatformTime::Seconds();
if (FlippedFrame.PresentIndex > SyncFrame)
{
// A new frame has flipped
SyncFrame = FlippedFrame.PresentIndex;
SyncTime = FlippedFrame.VBlankTimeInSeconds;
bForceFlipSync = CVarRHISyncAllowEarlyKick.GetValueOnAnyThread() == 0;
}
else if (SyncInterval != 0 && !bForceFlipSync && (CurrentTimeInSeconds > ExpectedNextFlipTimeInSeconds))
{
// We've missed a flip. Signal the next frame
// anyway to optimistically recover from a hitch.
SyncFrame = FlippedFrame.PresentIndex + 1;
SyncTime = CurrentTimeInSeconds;
}
bool bUpdateRHIFrameTime = false;
// Complete any relevant task events.
FScopeLock Lock(&CS);
for (int32 PairIndex = FramePairs.Num() - 1; PairIndex >= 0; --PairIndex)
{
auto& Pair = FramePairs[PairIndex];
if (Pair.PresentIndex <= SyncFrame)
{
FramePairs.RemoveAtSwap(PairIndex);
bUpdateRHIFrameTime = true;
}
}
if(bUpdateRHIFrameTime)
{
RHICalculateFrameTime();
}
}
return 0;
}
void FRHIFrameFlipTrackingRunnable::Stop()
{
bRun = false;
#if USE_FRAME_OFFSET_THREAD
FRHIFrameOffsetThread::Signal();
#else
GDynamicRHI->RHISignalFlipEvent();
#endif
}
void FRHIFrameFlipTrackingRunnable::Initialize()
{
if ( ! FPlatformMisc::UseRenderThread() )
{
return;
}
check(Thread == nullptr);
bInitialized = true;
bRun = true;
Thread = FRunnableThread::Create(&Singleton, TEXT("RHIFrameFlipThread"), 0, TPri_AboveNormal);
}
void FRHIFrameFlipTrackingRunnable::Shutdown()
{
if ( ! FPlatformMisc::UseRenderThread() )
{
return;
}
if (!bInitialized)
{
return;
}
bInitialized = false;
if (Thread)
{
Thread->Kill(true);
delete Thread;
Thread = nullptr;
}
FScopeLock Lock(&Singleton.CS);
Singleton.FramePairs.Empty();
#if USE_FRAME_OFFSET_THREAD
FRHIFrameOffsetThread::Shutdown();
#endif
}
void FRHIFrameFlipTrackingRunnable::TriggerTaskEventOnFlip(uint64 PresentIndex, UE::Tasks::FTaskEvent Event)
{
if ( ! FPlatformMisc::UseRenderThread() )
{
return;
}
FScopeLock Lock(&Singleton.CS);
if (Thread)
{
Singleton.FramePairs.Emplace(PresentIndex, MoveTemp(Event));
#if USE_FRAME_OFFSET_THREAD
FRHIFrameOffsetThread::Signal();
#else
GDynamicRHI->RHISignalFlipEvent();
#endif
}
else
{
// Platform does not support flip tracking.
// Signal the event now...
TRACE_CPUPROFILER_EVENT_SCOPE(SyncTrigger_Swapchain);
Event.Trigger();
}
}
FRunnableThread* FRHIFrameFlipTrackingRunnable::Thread;
FRHIFrameFlipTrackingRunnable FRHIFrameFlipTrackingRunnable::Singleton;
bool FRHIFrameFlipTrackingRunnable::bInitialized = false;
bool FRHIFrameFlipTrackingRunnable::bRun = false;
uint32 RHIGetSyncInterval()
{
return FMath::Max(CVarRHISyncInterval.GetValueOnAnyThread(), 0);
}
float RHIGetSyncSlackMS()
{
#if USE_FRAME_OFFSET_THREAD
const float SyncSlackMS = CVarRHISyncSlackMS.GetValueOnAnyThread();
#else // #if USE_FRAME_OFFSET_THREAD
const float SyncSlackMS = RHIGetSyncInterval() / float(FPlatformMisc::GetMaxRefreshRate()) * 1000.f; // Sync slack is entire frame interval if we aren't using the frame offset system
#endif // #else // #if USE_FRAME_OFFSET_THREAD
return SyncSlackMS;
}
bool RHIGetSyncAllowVariable()
{
return CVarRHISyncAllowVariable.GetValueOnAnyThread() != 0;
}
void RHIGetPresentThresholds(float& OutTopPercent, float& OutBottomPercent)
{
OutTopPercent = FMath::Clamp(CVarRHIPresentThresholdTop.GetValueOnAnyThread(), 0.0f, 1.0f);
OutBottomPercent = FMath::Clamp(CVarRHIPresentThresholdBottom.GetValueOnAnyThread(), 0.0f, 1.0f);
}
void RHITriggerTaskEventOnFlip(uint64 PresentIndex, const UE::Tasks::FTaskEvent& Event)
{
FRHIFrameFlipTrackingRunnable::TriggerTaskEventOnFlip(PresentIndex, Event);
}
void RHISetFrameDebugInfo(uint64 PresentIndex, uint64 FrameIndex, uint64 InputTime)
{
#if USE_FRAME_OFFSET_THREAD
FRHIFrameOffsetThread::SetFrameDebugInfo(PresentIndex, FrameIndex, InputTime);
#endif
}
void RHISetVsyncDebugInfo(FRHIFlipDetails& NewFlipFrame)
{
#if !UE_BUILD_SHIPPING && USE_FRAME_OFFSET_THREAD && PLATFORM_SUPPORTS_FLIP_TRACKING
check(FRHIFrameOffsetThread::Thread == nullptr);
FScopeLock Lock(&FRHIFrameOffsetThread::Singleton.CS);
FRHIFrameOffsetThread::Singleton.LastFlipFrame = NewFlipFrame;
for (int32 DebugInfoIndex = FRHIFrameOffsetThread::Singleton.FrameDebugInfos.Num() - 1; DebugInfoIndex >= 0; --DebugInfoIndex)
{
auto const& DebugInfo = FRHIFrameOffsetThread::Singleton.FrameDebugInfos[DebugInfoIndex];
//Note that we are using PresentIndex-1 since RHISetFrameDebugInfo is setting GRHIPresentCounter-1 due to other platforms setup
if ((NewFlipFrame.PresentIndex-1) == DebugInfo.PresentIndex)
{
uint64 VBlankTimeInCycle = NewFlipFrame.VBlankTimeInCycles;
if (VBlankTimeInCycle > DebugInfo.InputTime)
{
GInputLatencyTime = VBlankTimeInCycle - DebugInfo.InputTime;
}
if (GVsyncInformationInsights)
{
TRACE_BOOKMARK_CYCLES(VBlankTimeInCycle, TEXT("Vsync P:%" UINT64_FMT " F:%" UINT64_FMT " Lat:%.2f"), DebugInfo.PresentIndex, DebugInfo.FrameIndex, FPlatformTime::ToMilliseconds64(GInputLatencyTime))
}
}
if (DebugInfo.PresentIndex <= NewFlipFrame.PresentIndex)
{
FRHIFrameOffsetThread::Singleton.FrameDebugInfos.RemoveAtSwap(DebugInfoIndex);
}
}
#endif
}
void RHIInitializeFlipTracking()
{
#if USE_FRAME_OFFSET_THREAD
FRHIFrameOffsetThread::Initialize();
#endif
FRHIFrameFlipTrackingRunnable::Initialize();
}
void RHICalculateFrameTime()
{
double CurrentTimeInSeconds = FPlatformTime::Seconds();
GRHIFrameTimeMS = (float)((CurrentTimeInSeconds - GLastRHITimeInSeconds) * 1000.0);
GLastRHITimeInSeconds = CurrentTimeInSeconds;
}
float RHIGetFrameTime()
{
return GRHIFrameTimeMS;
}
void RHIShutdownFlipTracking()
{
FRHIFrameFlipTrackingRunnable::Shutdown();
#if USE_FRAME_OFFSET_THREAD
FRHIFrameOffsetThread::Shutdown();
#endif
}
ERHIAccess RHIGetDefaultResourceState(ETextureCreateFlags InUsage, bool bInHasInitialData)
{
// By default assume it can be bound for reading
ERHIAccess ResourceState = ERHIAccess::SRVMask;
if (!bInHasInitialData)
{
if (EnumHasAnyFlags(InUsage, TexCreate_RenderTargetable))
{
ResourceState = ERHIAccess::RTV;
}
else if (EnumHasAnyFlags(InUsage, TexCreate_DepthStencilTargetable))
{
ResourceState = ERHIAccess::DSVWrite | ERHIAccess::DSVRead;
}
else if (EnumHasAnyFlags(InUsage, TexCreate_UAV))
{
ResourceState = ERHIAccess::UAVMask;
}
else if (EnumHasAnyFlags(InUsage, TexCreate_Presentable))
{
ResourceState = ERHIAccess::Present;
}
else if (EnumHasAnyFlags(InUsage, TexCreate_ShaderResource))
{
ResourceState = ERHIAccess::SRVMask;
}
else if (EnumHasAnyFlags(InUsage, TexCreate_Foveation))
{
ResourceState = ERHIAccess::ShadingRateSource;
}
}
check(ResourceState != ERHIAccess::Unknown);
return ResourceState;
}
ERHIAccess RHIGetDefaultResourceState(EBufferUsageFlags InUsage, bool bInHasInitialData)
{
// Default reading state is different per buffer type
ERHIAccess DefaultReadingState = ERHIAccess::Unknown;
if (EnumHasAnyFlags(InUsage, BUF_IndexBuffer))
{
DefaultReadingState = ERHIAccess::VertexOrIndexBuffer;
}
if (EnumHasAnyFlags(InUsage, BUF_VertexBuffer))
{
// Could be vertex buffer or normal DataBuffer
DefaultReadingState = DefaultReadingState | ERHIAccess::VertexOrIndexBuffer | ERHIAccess::SRVMask;
}
if (EnumHasAnyFlags(InUsage, BUF_StructuredBuffer))
{
DefaultReadingState = DefaultReadingState | ERHIAccess::SRVMask;
}
if (EnumHasAnyFlags(InUsage, BUF_AccelerationStructure))
{
DefaultReadingState = DefaultReadingState | ERHIAccess::BVHRead;
}
// Vertex and index buffers might not have the BUF_ShaderResource flag set and just assume
// they are readable by default
ERHIAccess ResourceState = (!EnumHasAnyFlags(DefaultReadingState, ERHIAccess::VertexOrIndexBuffer)) ? ERHIAccess::Unknown : DefaultReadingState;
// SRV when we have initial data because we can sample the buffer then
if (bInHasInitialData)
{
ResourceState = DefaultReadingState;
}
else
{
if (EnumHasAnyFlags(InUsage, BUF_UnorderedAccess))
{
ResourceState = ERHIAccess::UAVMask;
}
else if (EnumHasAnyFlags(InUsage, BUF_AccelerationStructure))
{
ResourceState = ERHIAccess::BVHWrite;
}
else if (EnumHasAnyFlags(InUsage, BUF_ShaderResource))
{
ResourceState = DefaultReadingState | ERHIAccess::SRVMask;
}
}
check(ResourceState != ERHIAccess::Unknown);
return ResourceState;
}
void DecodeRenderTargetMode(ESimpleRenderTargetMode Mode, ERenderTargetLoadAction& ColorLoadAction, ERenderTargetStoreAction& ColorStoreAction, ERenderTargetLoadAction& DepthLoadAction, ERenderTargetStoreAction& DepthStoreAction, ERenderTargetLoadAction& StencilLoadAction, ERenderTargetStoreAction& StencilStoreAction, FExclusiveDepthStencil DepthStencilUsage)
{
// set defaults
ColorStoreAction = ERenderTargetStoreAction::EStore;
DepthStoreAction = ERenderTargetStoreAction::EStore;
StencilStoreAction = ERenderTargetStoreAction::EStore;
switch (Mode)
{
case ESimpleRenderTargetMode::EExistingColorAndDepth:
ColorLoadAction = ERenderTargetLoadAction::ELoad;
DepthLoadAction = ERenderTargetLoadAction::ELoad;
break;
case ESimpleRenderTargetMode::EUninitializedColorAndDepth:
ColorLoadAction = ERenderTargetLoadAction::ENoAction;
DepthLoadAction = ERenderTargetLoadAction::ENoAction;
break;
case ESimpleRenderTargetMode::EUninitializedColorExistingDepth:
ColorLoadAction = ERenderTargetLoadAction::ENoAction;
DepthLoadAction = ERenderTargetLoadAction::ELoad;
break;
case ESimpleRenderTargetMode::EUninitializedColorClearDepth:
ColorLoadAction = ERenderTargetLoadAction::ENoAction;
DepthLoadAction = ERenderTargetLoadAction::EClear;
break;
case ESimpleRenderTargetMode::EClearColorExistingDepth:
ColorLoadAction = ERenderTargetLoadAction::EClear;
DepthLoadAction = ERenderTargetLoadAction::ELoad;
break;
case ESimpleRenderTargetMode::EClearColorAndDepth:
ColorLoadAction = ERenderTargetLoadAction::EClear;
DepthLoadAction = ERenderTargetLoadAction::EClear;
break;
case ESimpleRenderTargetMode::EExistingContents_NoDepthStore:
ColorLoadAction = ERenderTargetLoadAction::ELoad;
DepthLoadAction = ERenderTargetLoadAction::ELoad;
DepthStoreAction = ERenderTargetStoreAction::ENoAction;
break;
case ESimpleRenderTargetMode::EExistingColorAndClearDepth:
ColorLoadAction = ERenderTargetLoadAction::ELoad;
DepthLoadAction = ERenderTargetLoadAction::EClear;
break;
case ESimpleRenderTargetMode::EExistingColorAndDepthAndClearStencil:
ColorLoadAction = ERenderTargetLoadAction::ELoad;
DepthLoadAction = ERenderTargetLoadAction::ELoad;
break;
default:
UE_LOG(LogRHI, Fatal, TEXT("Using a ESimpleRenderTargetMode that wasn't decoded in DecodeRenderTargetMode [value = %d]"), (int32)Mode);
}
StencilLoadAction = DepthLoadAction;
if (!DepthStencilUsage.IsUsingDepth())
{
DepthLoadAction = ERenderTargetLoadAction::ENoAction;
}
//if we aren't writing to depth, there's no reason to store it back out again. Should save some bandwidth on mobile platforms.
if (!DepthStencilUsage.IsDepthWrite())
{
DepthStoreAction = ERenderTargetStoreAction::ENoAction;
}
if (!DepthStencilUsage.IsUsingStencil())
{
StencilLoadAction = ERenderTargetLoadAction::ENoAction;
}
//if we aren't writing to stencil, there's no reason to store it back out again. Should save some bandwidth on mobile platforms.
if (!DepthStencilUsage.IsStencilWrite())
{
StencilStoreAction = ERenderTargetStoreAction::ENoAction;
}
}
EGpuVendorId RHIGetPreferredAdapterVendor()
{
if (FParse::Param(FCommandLine::Get(), TEXT("preferAMD")))
{
return EGpuVendorId::Amd;
}
if (FParse::Param(FCommandLine::Get(), TEXT("preferIntel")))
{
return EGpuVendorId::Intel;
}
if (FParse::Param(FCommandLine::Get(), TEXT("preferNvidia")))
{
return EGpuVendorId::Nvidia;
}
if (FParse::Param(FCommandLine::Get(), TEXT("preferMS")) || FParse::Param(FCommandLine::Get(), TEXT("preferMicrosoft")))
{
return EGpuVendorId::Microsoft;
}
return EGpuVendorId::Unknown;
}