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

869 lines
29 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
D3D11Viewport.cpp: D3D viewport RHI implementation.
=============================================================================*/
#include "D3D11Viewport.h"
#include "D3D11RHIPrivate.h"
#include "RenderCore.h"
#include "HDRHelper.h"
#include "Engine/RendererSettings.h"
#include "HAL/ThreadHeartBeat.h"
#include "RHIUtilities.h"
#ifndef D3D11_WITH_DWMAPI
#define D3D11_WITH_DWMAPI 1
#endif
#if D3D11_WITH_DWMAPI
#include "Windows/AllowWindowsPlatformTypes.h"
#include <dwmapi.h>
#endif //D3D11_WITH_DWMAPI
#ifndef DXGI_PRESENT_ALLOW_TEARING
#define DXGI_PRESENT_ALLOW_TEARING 0x00000200UL
#endif
#ifndef DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING
#define DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING 2048
#endif
/**
* RHI console variables used by viewports.
*/
namespace RHIConsoleVariables
{
int32 bSyncWithDWM = 0;
static FAutoConsoleVariableRef CVarSyncWithDWM(
TEXT("RHI.SyncWithDWM"),
bSyncWithDWM,
TEXT("If true, synchronize with the desktop window manager for vblank."),
ECVF_RenderThreadSafe
);
float RefreshPercentageBeforePresent = 1.0f;
static FAutoConsoleVariableRef CVarRefreshPercentageBeforePresent(
TEXT("RHI.RefreshPercentageBeforePresent"),
RefreshPercentageBeforePresent,
TEXT("The percentage of the refresh period to wait before presenting."),
ECVF_RenderThreadSafe
);
int32 TargetRefreshRate = 0;
static FAutoConsoleVariableRef CVarTargetRefreshRate(
TEXT("RHI.TargetRefreshRate"),
TargetRefreshRate,
TEXT("If non-zero, the display will never update more often than the target refresh rate (in Hz)."),
ECVF_RenderThreadSafe
);
float SyncRefreshThreshold = 1.05f;
static FAutoConsoleVariableRef CVarSyncRefreshThreshold(
TEXT("RHI.SyncRefreshThreshold"),
SyncRefreshThreshold,
TEXT("Threshold for time above which vsync will be disabled as a percentage of the refresh rate."),
ECVF_RenderThreadSafe
);
int32 MaxSyncCounter = 8;
static FAutoConsoleVariableRef CVarMaxSyncCounter(
TEXT("RHI.MaxSyncCounter"),
MaxSyncCounter,
TEXT("Maximum sync counter to smooth out vsync transitions."),
ECVF_RenderThreadSafe
);
int32 SyncThreshold = 7;
static FAutoConsoleVariableRef CVarSyncThreshold(
TEXT("RHI.SyncThreshold"),
SyncThreshold,
TEXT("Number of consecutive 'fast' frames before vsync is enabled."),
ECVF_RenderThreadSafe
);
int32 MaximumFrameLatency = 3;
static FAutoConsoleVariableRef CVarMaximumFrameLatency(
TEXT("RHI.MaximumFrameLatency"),
MaximumFrameLatency,
TEXT("Number of frames that can be queued for render."),
ECVF_RenderThreadSafe
);
};
/**
* Returns the current swap chain flags but with the same tearing policy used during construction.
*/
uint32 FD3D11Viewport::GetSwapChainFlags()
{
uint32 SwapChainFlags = GSwapChainFlags;
// Ensure AllowTearing consistency or ResizeBuffers will fail with E_INVALIDARG
if (bAllowTearing != !!(SwapChainFlags & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING))
{
SwapChainFlags ^= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING;
}
return SwapChainFlags;
}
/**
* Creates a FD3D11Surface to represent a swap chain's back buffer.
*/
FD3D11Texture* FD3D11Viewport::GetSwapChainSurface(FD3D11DynamicRHI* D3DRHI, EPixelFormat PixelFormat, uint32 SizeX, uint32 SizeY, IDXGISwapChain* SwapChain)
{
// Grab the back buffer
TRefCountPtr<ID3D11Texture2D> BackBufferResource;
if (SwapChain)
{
VERIFYD3D11RESULT_EX(SwapChain->GetBuffer(0, IID_ID3D11Texture2D, (void**)BackBufferResource.GetInitReference()), D3DRHI->GetDevice());
}
else
{
// Create custom back buffer texture as no swap chain is created in pixel streaming windowless mode
DXGI_FORMAT TextureFormat = GetRenderTargetFormat(PixelFormat);
D3D11_TEXTURE2D_DESC TextureDesc;
FMemory::Memzero(TextureDesc);
TextureDesc.Width = SizeX;
TextureDesc.Height = SizeY;
TextureDesc.MipLevels = 1;
TextureDesc.ArraySize = 1;
TextureDesc.Format = TextureFormat;
TextureDesc.SampleDesc.Count = 1;
TextureDesc.SampleDesc.Quality = 0;
TextureDesc.Usage = D3D11_USAGE_DEFAULT;
TextureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
VERIFYD3D11RESULT_EX(D3DRHI->GetDevice()->CreateTexture2D(&TextureDesc, NULL, BackBufferResource.GetInitReference()), D3DRHI->GetDevice());
}
// create the render target view
TRefCountPtr<ID3D11RenderTargetView> BackBufferRenderTargetView;
TRefCountPtr<ID3D11RenderTargetView> BackBufferRenderTargetViewRight; // right eye RTV
// dx11.1 active stereoscopy initialization
if (D3DRHI->IsQuadBufferStereoEnabled())
{
// left
CD3D11_RENDER_TARGET_VIEW_DESC RTVDescCD3D11_left(D3D11_RTV_DIMENSION_TEXTURE2DARRAY, DXGI_FORMAT_R10G10B10A2_UNORM, 0, 0, 1);
VERIFYD3D11RESULT_EX(D3DRHI->GetDevice()->CreateRenderTargetView(BackBufferResource, &RTVDescCD3D11_left, BackBufferRenderTargetView.GetInitReference()), D3DRHI->GetDevice());
// right
CD3D11_RENDER_TARGET_VIEW_DESC renderTargetViewRightDesc_right(D3D11_RTV_DIMENSION_TEXTURE2DARRAY, DXGI_FORMAT_R10G10B10A2_UNORM, 0, 1, 1);
VERIFYD3D11RESULT_EX(D3DRHI->GetDevice()->CreateRenderTargetView(BackBufferResource, &renderTargetViewRightDesc_right, BackBufferRenderTargetViewRight.GetInitReference()), D3DRHI->GetDevice());
}
else
{
D3D11_RENDER_TARGET_VIEW_DESC RTVDesc;
RTVDesc.Format = DXGI_FORMAT_UNKNOWN;
RTVDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
RTVDesc.Texture2D.MipSlice = 0;
VERIFYD3D11RESULT_EX(D3DRHI->GetDevice()->CreateRenderTargetView(BackBufferResource, &RTVDesc, BackBufferRenderTargetView.GetInitReference()), D3DRHI->GetDevice());
}
D3D11_TEXTURE2D_DESC TextureDesc;
BackBufferResource->GetDesc(&TextureDesc);
TArray<TRefCountPtr<ID3D11RenderTargetView> > RenderTargetViews;
RenderTargetViews.Add(BackBufferRenderTargetView);
// add right eye render target view
if (D3DRHI->IsQuadBufferStereoEnabled())
{
RenderTargetViews.Add(BackBufferRenderTargetViewRight);
}
// create a shader resource view to allow using the backbuffer as a texture
TRefCountPtr<ID3D11ShaderResourceView> BackBufferShaderResourceView;
D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
SRVDesc.Format = DXGI_FORMAT_UNKNOWN;
SRVDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
SRVDesc.Texture2D.MostDetailedMip = 0;
SRVDesc.Texture2D.MipLevels = 1;
VERIFYD3D11RESULT_EX(D3DRHI->GetDevice()->CreateShaderResourceView(BackBufferResource,&SRVDesc,BackBufferShaderResourceView.GetInitReference()), D3DRHI->GetDevice());
const FRHITextureCreateDesc CreateDesc =
FRHITextureCreateDesc::Create2D(TEXT("FD3D11Viewport::GetSwapChainSurface"), TextureDesc.Width, TextureDesc.Height, PixelFormat)
.SetFlags(ETextureCreateFlags::RenderTargetable)
.DetermineInititialState();
FD3D11Texture* NewTexture = new FD3D11Texture(
CreateDesc,
BackBufferResource,
BackBufferShaderResourceView,
1,
false,
RenderTargetViews,
{}
);
return NewTexture;
}
FD3D11Viewport::~FD3D11Viewport()
{
check(IsInRHIThread() || IsInRenderingThread());
// If the swap chain was in fullscreen mode, switch back to windowed before releasing the swap chain.
// DXGI throws an error otherwise.
if (SwapChain)
{
VERIFYD3D11RESULT_EX(SwapChain->SetFullscreenState(false, NULL), D3DRHI->GetDevice());
}
D3DRHI->Viewports.Remove(this);
}
DXGI_MODE_DESC FD3D11Viewport::SetupDXGI_MODE_DESC() const
{
DXGI_MODE_DESC Ret;
Ret.Width = SizeX;
Ret.Height = SizeY;
Ret.RefreshRate.Numerator = 0; // illamas: use 0 to avoid a potential mismatch with hw
Ret.RefreshRate.Denominator = 0; // illamas: ditto
Ret.Format = GetRenderTargetFormat(PixelFormat);
Ret.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
Ret.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
return Ret;
}
void FD3D11Viewport::Resize(uint32 InSizeX, uint32 InSizeY, bool bInIsFullscreen, EPixelFormat PreferredPixelFormat)
{
// Unbind any dangling references to resources
D3DRHI->SetRenderTargets(0, nullptr, nullptr);
D3DRHI->ClearState();
D3DRHI->GetDeviceContext()->Flush(); // Potential perf hit
if (IsValidRef(CustomPresent))
{
CustomPresent->OnBackBufferResize();
}
// Release our backbuffer reference, as required by DXGI before calling ResizeBuffers.
if (IsValidRef(BackBuffer))
{
check(BackBuffer->GetRefCount() == 1);
checkComRefCount(BackBuffer->GetResource(),1);
checkComRefCount(BackBuffer->GetRenderTargetView(0, -1),1);
checkComRefCount(BackBuffer->GetShaderResourceView(),1);
}
BackBuffer.SafeRelease();
// Flush the outstanding GPU work and wait for it to complete.
FlushRenderingCommands();
// Make sure we use a format the current device supports.
PreferredPixelFormat = D3DRHI->GetDisplayFormat(PreferredPixelFormat);
const FD3D11ResizeViewportState OldState{ SizeX, SizeY, GetRenderTargetFormat(PixelFormat), bIsFullscreen };
const FD3D11ResizeViewportState NewState{ InSizeX, InSizeY, GetRenderTargetFormat(PreferredPixelFormat), bInIsFullscreen };
if(SizeX != InSizeX || SizeY != InSizeY || PixelFormat != PreferredPixelFormat)
{
SizeX = InSizeX;
SizeY = InSizeY;
PixelFormat = PreferredPixelFormat;
check(SizeX > 0);
check(SizeY > 0);
if (bNeedSwapChain)
{
// Resize the swap chain.
const UINT SwapChainFlags = GetSwapChainFlags();
const DXGI_FORMAT RenderTargetFormat = GetRenderTargetFormat(PixelFormat);
// Resize all existing buffers, don't change count
VERIFYD3D11RESIZEVIEWPORTRESULT(SwapChain->ResizeBuffers(0, SizeX, SizeY, RenderTargetFormat, SwapChainFlags), OldState, NewState, D3DRHI->GetDevice());
if (bInIsFullscreen)
{
DXGI_MODE_DESC BufferDesc = SetupDXGI_MODE_DESC();
if (FAILED(SwapChain->ResizeTarget(&BufferDesc)))
{
ResetSwapChainInternal(true);
VERIFYD3D11RESIZEVIEWPORTRESULT(SwapChain->ResizeBuffers(0, SizeX, SizeY, RenderTargetFormat, SwapChainFlags), OldState, NewState, D3DRHI->GetDevice());
}
}
}
}
if(bIsFullscreen != bInIsFullscreen)
{
bIsFullscreen = bInIsFullscreen;
ValidState = VIEWPORT_INVALID;
if (bNeedSwapChain)
{
// Use ConditionalResetSwapChain to call SetFullscreenState, to handle the failure case.
// Ignore the viewport's focus state; since Resize is called as the result of a user action we assume authority without waiting for Focus.
ResetSwapChainInternal(true);
if (!bIsFullscreen)
{
// When exiting fullscreen, make sure that the window has the correct size. This is necessary in the following scenario:
// * we enter exclusive fullscreen with a resolution lower than the monitor's native resolution, or from windowed with a window size smaller than the screen
// * the application loses focus, so Slate asks us to switch to Windowed Fullscreen (see FSlateRenderer::IsViewportFullscreen)
// * InSizeX and InSizeY are given to us as the monitor resolution, so we resize the buffers to the correct resolution below
// * however, the target still has the smaller size, because Slate doesn't know it has to resize the window too (as far as it's concerned, it's already the right size)
// * therefore, we need to call ResizeTarget, which in windowed mode behaves like SetWindowPos.
const DXGI_MODE_DESC BufferDesc = SetupDXGI_MODE_DESC();
SwapChain->ResizeTarget(&BufferDesc);
}
DXGI_FORMAT RenderTargetFormat = GetRenderTargetFormat(PixelFormat);
VERIFYD3D11RESIZEVIEWPORTRESULT(SwapChain->ResizeBuffers(0, SizeX, SizeY, RenderTargetFormat, GetSwapChainFlags()), OldState, NewState, D3DRHI->GetDevice());
}
}
RECT WindowRect = {};
GetWindowRect(WindowHandle, &WindowRect);
FVector2D WindowTopLeft((float)WindowRect.left, (float)WindowRect.top);
FVector2D WindowBottomRight((float)WindowRect.right, (float)WindowRect.bottom);
bool bHDREnabled;
HDRGetMetaData(DisplayOutputFormat, DisplayColorGamut, bHDREnabled, WindowTopLeft, WindowBottomRight, (void*)WindowHandle);
// Float RGBA backbuffers are requested whenever HDR mode is desired
if (bHDREnabled)
{
EnableHDR();
}
else
{
ShutdownHDR();
}
// Create a RHI surface to represent the viewport's back buffer.
BackBuffer = GetSwapChainSurface(D3DRHI, PixelFormat, SizeX, SizeY, SwapChain);
}
/** Returns true if desktop composition is enabled. */
static bool IsCompositionEnabled()
{
BOOL bDwmEnabled = false;
#if D3D11_WITH_DWMAPI
DwmIsCompositionEnabled(&bDwmEnabled);
#endif //D3D11_WITH_DWMAPI
return !!bDwmEnabled;
}
/** Presents the swap chain checking the return result. */
bool FD3D11Viewport::PresentChecked(IRHICommandContext& RHICmdContext, int32 SyncInterval)
{
HRESULT Result = S_OK;
bool bNeedNativePresent = true;
#if !UE_BUILD_SHIPPING && PLATFORM_SUPPORTS_FLIP_TRACKING
if (SwapChain.IsValid())
{
static FRHIFlipDetails LastFlipFrame;
static DXGI_FRAME_STATISTICS LastStats = { 0 };
HRESULT GetStatHR;
DXGI_FRAME_STATISTICS stats = { 0 };
while (SUCCEEDED(GetStatHR = SwapChain->GetFrameStatistics(&stats)) && (stats.PresentCount > LastFlipFrame.PresentIndex))
{
FRHIFlipDetails NewFlipFrame;
NewFlipFrame.PresentIndex = stats.PresentCount;
NewFlipFrame.VBlankTimeInCycles = stats.SyncQPCTime.QuadPart;
RHISetVsyncDebugInfo(NewFlipFrame);
LastFlipFrame = NewFlipFrame;
LastStats = stats;
}
}
#endif
if (IsValidRef(CustomPresent))
{
SCOPE_CYCLE_COUNTER(STAT_D3D11CustomPresentTime);
bNeedNativePresent = CustomPresent->Present(RHICmdContext, SyncInterval);
}
if (bNeedNativePresent)
{
if (SwapChain.IsValid())
{
// Check if the viewport's swap chain has been invalidated by DXGI.
BOOL bSwapChainFullscreenState;
TRefCountPtr<IDXGIOutput> SwapChainOutput;
SwapChain->GetFullscreenState(&bSwapChainFullscreenState, SwapChainOutput.GetInitReference());
// Can't compare BOOL with bool...
if ((!!bSwapChainFullscreenState) != bIsFullscreen)
{
ValidState = (VIEWPORT_INVALID | VIEWPORT_FULLSCREEN_LOST);
}
else
{
// Present the back buffer to the viewport window.
uint32 Flags = 0;
if( (GetSwapChainFlags() & DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) != 0 && !SyncInterval && !bIsFullscreen )
{
Flags |= DXGI_PRESENT_ALLOW_TEARING;
}
{
FRenderThreadIdleScope IdleScope(ERenderThreadIdleTypes::WaitingForGPUPresent);
TRACE_CPUPROFILER_EVENT_SCOPE(D3D11_Present);
Result = SwapChain->Present(SyncInterval, Flags);
}
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST
extern int32 GLogDX11RTRebinds;
extern FThreadSafeCounter GDX11RTRebind;
extern FThreadSafeCounter GDX11CommitGraphicsResourceTables;
if (GLogDX11RTRebinds)
{
static int Counter = 0;
Counter++;
if (Counter == 60)
{
Counter = 0;
int32 RTRebinds = GDX11RTRebind.Set(0);
int32 CommitGraphicsResourceTables = GDX11CommitGraphicsResourceTables.Set(0);
FGenericPlatformMisc::LowLevelOutputDebugStringf(TEXT("RT Rebind %6.2f Commit Graphics Resource Tables %6.2f\n"), RTRebinds / 60.f, CommitGraphicsResourceTables / 60.f);
}
}
#endif
}
}
if (IsValidRef(CustomPresent))
{
CustomPresent->PostPresent();
}
}
FThreadHeartBeat::Get().PresentFrame();
if (FAILED(Result))
{
PresentFailCount++;
UE_LOG(LogRHI, Error, TEXT("Present Fail Count %i"), PresentFailCount);
DXGI_SWAP_CHAIN_DESC Desc;
UE_LOG(LogRHI, Error, TEXT("SyncInterval %i"), SyncInterval);
if (SwapChain.IsValid())
{
if (!FAILED(SwapChain->GetDesc(&Desc)))
{
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferDesc.Width %i"), Desc.BufferDesc.Width);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferDesc.Height %i"), Desc.BufferDesc.Height);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferDesc.RefreshRate.Numerator %i"), Desc.BufferDesc.RefreshRate.Numerator);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferDesc.RefreshRate.Denominator %i"), Desc.BufferDesc.RefreshRate.Denominator);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferDesc.Format %i"), Desc.BufferDesc.Format);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferDesc.ScanlineOrdering %i"), Desc.BufferDesc.ScanlineOrdering);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferDesc.Scaling %i"), Desc.BufferDesc.Scaling);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.SampleDesc.Count %i"), Desc.SampleDesc.Count);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.SampleDesc.Quality %i"), Desc.SampleDesc.Quality);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferUsage %i"), Desc.BufferUsage);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.BufferCount %i"), Desc.BufferCount);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.OutputWindow %p"), Desc.OutputWindow);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.Windowed %s"), Desc.Windowed ? TEXT("true") : TEXT("false"));
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.SwapEffect %u"), Desc.SwapEffect);
UE_LOG(LogRHI, Error, TEXT("SwapChainDesc.Flags %u"), Desc.Flags);
}
}
if (PresentFailCount > 10)
{
VERIFYD3D11RESULT_EX(Result, D3DRHI->GetDevice());
}
else
{
ValidState = (VIEWPORT_INVALID | VIEWPORT_FULLSCREEN_LOST);
}
}
else
{
PresentFailCount = 0;
}
FThreadHeartBeat::Get().PresentFrame();
D3DRHI->GetDeviceContext()->OMSetRenderTargets(0,0,0);
UINT PresentID;
if ( (SwapChain.IsValid()) && (SUCCEEDED(SwapChain->GetLastPresentCount(&PresentID))) )
{
GRHIPresentCounter = PresentID;
}
else
{
GRHIPresentCounter++;
}
return bNeedNativePresent;
}
/** Blocks the CPU to synchronize with vblank by communicating with DWM. */
void FD3D11Viewport::PresentWithVsyncDWM(IRHICommandContext& RHICmdContext)
{
#if D3D11_WITH_DWMAPI
LARGE_INTEGER Cycles;
DWM_TIMING_INFO TimingInfo;
// Find out how long since we last flipped and query DWM for timing information.
QueryPerformanceCounter(&Cycles);
FMemory::Memzero(TimingInfo);
TimingInfo.cbSize = sizeof(DWM_TIMING_INFO);
// Starting at windows 8.1 null must be passed into this method for it to work. null also works on previous versions
DwmGetCompositionTimingInfo(nullptr, &TimingInfo);
uint64 QpcAtFlip = Cycles.QuadPart;
uint64 CyclesSinceLastFlip = Cycles.QuadPart - LastFlipTime;
float CPUTime = FPlatformTime::ToMilliseconds(CyclesSinceLastFlip);
float GPUTime = FPlatformTime::ToMilliseconds(TimingInfo.qpcFrameComplete - LastCompleteTime);
float DisplayRefreshPeriod = FPlatformTime::ToMilliseconds(TimingInfo.qpcRefreshPeriod);
// Find the smallest multiple of the refresh rate that is >= 33ms, our target frame rate.
float RefreshPeriod = DisplayRefreshPeriod;
if(RHIConsoleVariables::TargetRefreshRate > 0 && RefreshPeriod > 1.0f)
{
while(RefreshPeriod - (1000.0f / RHIConsoleVariables::TargetRefreshRate) < -1.0f)
{
RefreshPeriod *= 2.0f;
}
}
// If the last frame hasn't completed yet, we don't know how long the GPU took.
bool bValidGPUTime = (TimingInfo.cFrameComplete > LastFrameComplete);
if (bValidGPUTime)
{
GPUTime /= (float)(TimingInfo.cFrameComplete - LastFrameComplete);
}
// Update the sync counter depending on how much time it took to complete the previous frame.
float FrameTime = FMath::Max<float>(CPUTime, GPUTime);
if (FrameTime >= RHIConsoleVariables::SyncRefreshThreshold * RefreshPeriod)
{
SyncCounter--;
}
else if (bValidGPUTime)
{
SyncCounter++;
}
SyncCounter = FMath::Clamp<int32>(SyncCounter, 0, RHIConsoleVariables::MaxSyncCounter);
// If frames are being completed quickly enough, block for vsync.
bool bSync = (SyncCounter >= RHIConsoleVariables::SyncThreshold);
if (bSync)
{
// This flushes the previous present call and blocks until it is made available to DWM.
D3DRHI->GetDeviceContext()->Flush();
DwmFlush();
// We sleep a percentage of the remaining time. The trick is to get the
// present call in after the vblank we just synced for but with time to
// spare for the next vblank.
float MinFrameTime = RefreshPeriod * RHIConsoleVariables::RefreshPercentageBeforePresent;
float TimeToSleep;
do
{
QueryPerformanceCounter(&Cycles);
float TimeSinceFlip = FPlatformTime::ToMilliseconds(Cycles.QuadPart - LastFlipTime);
TimeToSleep = (MinFrameTime - TimeSinceFlip);
if (TimeToSleep > 0.0f)
{
FPlatformProcess::Sleep(TimeToSleep * 0.001f);
}
}
while (TimeToSleep > 0.0f);
}
// Present.
PresentChecked(RHICmdContext, /*SyncInterval=*/ 0);
// If we are forcing <= 30Hz, block the CPU an additional amount of time if needed.
// This second block is only needed when RefreshPercentageBeforePresent < 1.0.
if (bSync)
{
LARGE_INTEGER LocalCycles;
float TimeToSleep;
bool bSaveCycles = false;
do
{
QueryPerformanceCounter(&LocalCycles);
float TimeSinceFlip = FPlatformTime::ToMilliseconds(LocalCycles.QuadPart - LastFlipTime);
TimeToSleep = (RefreshPeriod - TimeSinceFlip);
if (TimeToSleep > 0.0f)
{
bSaveCycles = true;
FPlatformProcess::Sleep(TimeToSleep * 0.001f);
}
}
while (TimeToSleep > 0.0f);
if (bSaveCycles)
{
Cycles = LocalCycles;
}
}
// If we are dropping vsync reset the counter. This provides a debounce time
// before which we try to vsync again.
if (!bSync && bSyncedLastFrame)
{
SyncCounter = 0;
}
if (bSync != bSyncedLastFrame || UE_LOG_ACTIVE(LogRHI,VeryVerbose))
{
UE_LOG(LogRHI,Verbose,TEXT("BlockForVsync[%d]: CPUTime:%.2fms GPUTime[%d]:%.2fms Blocked:%.2fms Pending/Complete:%d/%d"),
bSync,
CPUTime,
bValidGPUTime,
GPUTime,
FPlatformTime::ToMilliseconds(Cycles.QuadPart - QpcAtFlip),
TimingInfo.cFramePending,
TimingInfo.cFrameComplete);
}
// Remember if we synced, when the frame completed, etc.
bSyncedLastFrame = bSync;
LastFlipTime = Cycles.QuadPart;
LastFrameComplete = TimingInfo.cFrameComplete;
LastCompleteTime = TimingInfo.qpcFrameComplete;
#endif //D3D11_WITH_DWMAPI
}
bool FD3D11Viewport::Present(IRHICommandContext& RHICmdContext, bool bLockToVsync)
{
bool bNativelyPresented = true;
#if D3D11_WITH_DWMAPI
// We can't call Present if !bIsValid, as it waits a window message to be processed, but the main thread may not be pumping the message handler.
if(ValidState != 0 && SwapChain.IsValid())
{
// Check if the viewport's swap chain has been invalidated by DXGI.
BOOL bSwapChainFullscreenState;
TRefCountPtr<IDXGIOutput> SwapChainOutput;
VERIFYD3D11RESULT_EX(SwapChain->GetFullscreenState(&bSwapChainFullscreenState,SwapChainOutput.GetInitReference()), D3DRHI->GetDevice());
// Can't compare BOOL with bool...
if ( (!!bSwapChainFullscreenState) != bIsFullscreen )
{
ValidState = VIEWPORT_INVALID;
}
}
if (MaximumFrameLatency != RHIConsoleVariables::MaximumFrameLatency)
{
MaximumFrameLatency = RHIConsoleVariables::MaximumFrameLatency;
TRefCountPtr<IDXGIDevice1> DXGIDevice;
VERIFYD3D11RESULT_EX(D3DRHI->GetDevice()->QueryInterface(IID_IDXGIDevice, (void**)DXGIDevice.GetInitReference()), D3DRHI->GetDevice());
DXGIDevice->SetMaximumFrameLatency(MaximumFrameLatency);
}
if(0 != (ValidState & VIEWPORT_INVALID))
{
return false;
}
// When desktop composition is enabled, locking to vsync via the Present
// call is unreliable. Instead, communicate with the desktop window manager
// directly to enable vsync.
const bool bSyncWithDWM = bLockToVsync && !bIsFullscreen && RHIConsoleVariables::bSyncWithDWM && IsCompositionEnabled();
if (bSyncWithDWM)
{
PresentWithVsyncDWM(RHICmdContext);
}
else
#endif //D3D11_WITH_DWMAPI
{
// Present the back buffer to the viewport window.
bNativelyPresented = PresentChecked(RHICmdContext, bLockToVsync ? RHIGetSyncInterval() : 0);
}
return bNativelyPresented;
}
void* FD3D11Viewport::GetNativeSwapChain() const
{
return GetSwapChain();
}
void* FD3D11Viewport::GetNativeBackBufferTexture() const
{
return GetBackBuffer()->GetD3D11Texture2D();
}
void* FD3D11Viewport::GetNativeBackBufferRT() const
{
return GetBackBuffer()->GetRenderTargetView(0, 0);
}
/*=============================================================================
* The following RHI functions must be called from the main thread.
*=============================================================================*/
FViewportRHIRef FD3D11DynamicRHI::RHICreateViewport(void* WindowHandle,uint32 SizeX,uint32 SizeY,bool bIsFullscreen,EPixelFormat PreferredPixelFormat)
{
check( IsInGameThread() );
// Use a default pixel format if none was specified
if (PreferredPixelFormat == EPixelFormat::PF_Unknown)
{
static const auto CVarDefaultBackBufferPixelFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DefaultBackBufferPixelFormat"));
PreferredPixelFormat = EDefaultBackBufferPixelFormat::Convert2PixelFormat(EDefaultBackBufferPixelFormat::FromInt(CVarDefaultBackBufferPixelFormat->GetValueOnGameThread()));
}
return new FD3D11Viewport(this,(HWND)WindowHandle,SizeX,SizeY,bIsFullscreen,PreferredPixelFormat);
}
void FD3D11DynamicRHI::RHIResizeViewport(FRHIViewport* ViewportRHI,uint32 SizeX,uint32 SizeY,bool bIsFullscreen)
{
FD3D11Viewport* Viewport = ResourceCast(ViewportRHI);
check( IsInGameThread() );
Viewport->Resize(SizeX,SizeY,bIsFullscreen, PF_Unknown);
}
void FD3D11DynamicRHI::RHIResizeViewport(FRHIViewport* ViewportRHI, uint32 SizeX, uint32 SizeY, bool bIsFullscreen, EPixelFormat PreferredPixelFormat)
{
check(IsInGameThread());
// Use a default pixel format if none was specified
if (PreferredPixelFormat == EPixelFormat::PF_Unknown)
{
static const auto CVarDefaultBackBufferPixelFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DefaultBackBufferPixelFormat"));
PreferredPixelFormat = EDefaultBackBufferPixelFormat::Convert2PixelFormat(EDefaultBackBufferPixelFormat::FromInt(CVarDefaultBackBufferPixelFormat->GetValueOnGameThread()));
}
FD3D11Viewport* Viewport = ResourceCast(ViewportRHI);
Viewport->Resize(SizeX, SizeY, bIsFullscreen, PreferredPixelFormat);
}
void FD3D11DynamicRHI::RHITick( float DeltaTime )
{
check( IsInGameThread() );
// Check if any swap chains have been invalidated.
for(int32 ViewportIndex = 0; ViewportIndex < Viewports.Num(); ViewportIndex++)
{
Viewports[ViewportIndex]->ConditionalResetSwapChain(false);
}
}
/*=============================================================================
* Viewport functions.
*=============================================================================*/
void FD3D11DynamicRHI::RHIBeginDrawingViewport(FRHIViewport* ViewportRHI, FRHITexture* RenderTarget)
{
FD3D11Viewport* Viewport = ResourceCast(ViewportRHI);
SCOPE_CYCLE_COUNTER(STAT_D3D11PresentTime);
check(!DrawingViewport);
DrawingViewport = Viewport;
// Set the render target and viewport.
if( RenderTarget == NULL )
{
RenderTarget = Viewport->GetBackBuffer();
}
FRHIRenderTargetView View(RenderTarget, ERenderTargetLoadAction::ELoad);
SetRenderTargets(1,&View,nullptr);
// Set an initially disabled scissor rect.
RHISetScissorRect(false,0,0,0,0);
FRHICustomPresent* CustomPresent = Viewport->GetCustomPresent();
if (CustomPresent)
{
CustomPresent->BeginDrawing();
}
}
void FD3D11DynamicRHI::RHIEndDrawingViewport(FRHIViewport* ViewportRHI,bool bPresent,bool bLockToVsync)
{
++PresentCounter;
FD3D11Viewport* Viewport = ResourceCast(ViewportRHI);
SCOPE_CYCLE_COUNTER(STAT_D3D11PresentTime);
check(DrawingViewport.GetReference() == Viewport);
DrawingViewport = NULL;
// Clear references the device might have to resources.
CurrentDepthTexture = NULL;
CurrentDepthStencilTarget = NULL;
CurrentDSVAccessType = FExclusiveDepthStencil::DepthWrite_StencilWrite;
CurrentRenderTargets[0] = NULL;
for(uint32 RenderTargetIndex = 1;RenderTargetIndex < D3D11_SIMULTANEOUS_RENDER_TARGET_COUNT;++RenderTargetIndex)
{
CurrentRenderTargets[RenderTargetIndex] = NULL;
}
ClearAllShaderResources();
CommitRenderTargetsAndUAVs();
StateCache.SetVertexShader(nullptr);
uint16 NullStreamStrides[MaxVertexElementCount] = {0};
StateCache.SetStreamStrides(NullStreamStrides);
for (uint32 StreamIndex = 0; StreamIndex < MaxVertexElementCount; ++StreamIndex)
{
StateCache.SetStreamSource(nullptr, StreamIndex, 0, 0);
}
StateCache.SetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0);
CurrentResourceBoundAsIB = nullptr;
FMemory::Memzero(CurrentResourcesBoundAsVBs, sizeof(CurrentResourcesBoundAsVBs));
MaxBoundVertexBufferIndex = INDEX_NONE;
StateCache.SetPixelShader(nullptr);
StateCache.SetGeometryShader(nullptr);
// Compute Shader is set to NULL after each Dispatch call, so no need to clear it here
bool bNativelyPresented = true;
if (bPresent)
{
bNativelyPresented = Viewport->Present(*this, bLockToVsync);
}
if (bNativelyPresented)
{
static const auto CFinishFrameVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.FinishCurrentFrame"));
if (!CFinishFrameVar->GetValueOnRenderThread())
{
// Wait for the GPU to finish rendering the previous frame before finishing this frame.
Viewport->WaitForFrameEventCompletion();
Viewport->IssueFrameEvent();
}
else
{
// Finish current frame immediately to reduce latency
Viewport->IssueFrameEvent();
Viewport->WaitForFrameEventCompletion();
}
}
// If the input latency timer has been triggered, block until the GPU is completely
// finished displaying this frame and calculate the delta time.
if ( GInputLatencyTimer.RenderThreadTrigger )
{
Viewport->WaitForFrameEventCompletion();
uint32 EndTime = FPlatformTime::Cycles();
GInputLatencyTimer.DeltaTime = EndTime - GInputLatencyTimer.StartTime;
GInputLatencyTimer.RenderThreadTrigger = false;
}
}
void FD3D11DynamicRHI::RHIAdvanceFrameForGetViewportBackBuffer(FRHIViewport* Viewport)
{
}
FTextureRHIRef FD3D11DynamicRHI::RHIGetViewportBackBuffer(FRHIViewport* ViewportRHI)
{
FD3D11Viewport* Viewport = ResourceCast(ViewportRHI);
return Viewport->GetBackBuffer();
}
#if D3D11_WITH_DWMAPI
#include "Windows/HideWindowsPlatformTypes.h"
#endif //D3D11_WITH_DWMAPI