879 lines
29 KiB
C++
879 lines
29 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
D3D12Viewport.cpp: D3D viewport RHI implementation.
|
|
=============================================================================*/
|
|
|
|
#include "D3D12Viewport.h"
|
|
#include "D3D12RHIPrivate.h"
|
|
#include "RenderCore.h"
|
|
#include "Engine/RendererSettings.h"
|
|
#include "HDRHelper.h"
|
|
#include "DataDrivenShaderPlatformInfo.h"
|
|
#include "RHIUtilities.h"
|
|
|
|
namespace D3D12RHI
|
|
{
|
|
/**
|
|
* RHI console variables used by viewports.
|
|
*/
|
|
namespace RHIConsoleVariables
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
#if LOG_VIEWPORT_EVENTS
|
|
int32 LogViewportEvents = 1;
|
|
#else
|
|
int32 LogViewportEvents = 0;
|
|
#endif
|
|
static FAutoConsoleVariableRef CVarLogViewportEvents(
|
|
TEXT("D3D12.LogViewportEvents"),
|
|
LogViewportEvents,
|
|
TEXT("Log all the viewport events."),
|
|
ECVF_RenderThreadSafe
|
|
);
|
|
#endif
|
|
};
|
|
}
|
|
using namespace D3D12RHI;
|
|
|
|
FCriticalSection FD3D12Viewport::DXGIBackBufferLock;
|
|
|
|
/**
|
|
* Creates a FD3D12Surface to represent a swap chain's back buffer.
|
|
*/
|
|
FD3D12Texture* GetSwapChainSurface(FD3D12Device* Parent, EPixelFormat PixelFormat, uint32 SizeX, uint32 SizeY, IDXGISwapChain* SwapChain, uint32 BackBufferIndex, TRefCountPtr<ID3D12Resource> BackBufferResourceOverride)
|
|
{
|
|
verify(D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN || SwapChain == nullptr);
|
|
|
|
FD3D12Adapter* Adapter = Parent->GetParentAdapter();
|
|
|
|
// Grab the back buffer
|
|
TRefCountPtr<ID3D12Resource> BackBufferResource;
|
|
if (SwapChain)
|
|
{
|
|
#if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
VERIFYD3D12RESULT_EX(SwapChain->GetBuffer(BackBufferIndex, IID_PPV_ARGS(BackBufferResource.GetInitReference())), Parent->GetDevice());
|
|
#else // #if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
return nullptr;
|
|
#endif // #if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
}
|
|
else if (BackBufferResourceOverride.IsValid())
|
|
{
|
|
BackBufferResource = BackBufferResourceOverride;
|
|
}
|
|
else
|
|
{
|
|
const D3D12_HEAP_PROPERTIES HeapProps = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT, (uint32)Parent->GetGPUIndex(), Parent->GetGPUMask().GetNative());
|
|
|
|
// Create custom back buffer texture as no swap chain is created in pixel streaming windowless mode
|
|
D3D12_RESOURCE_DESC TextureDesc;
|
|
TextureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
|
TextureDesc.Alignment = 0;
|
|
TextureDesc.Width = SizeX;
|
|
TextureDesc.Height = SizeY;
|
|
TextureDesc.DepthOrArraySize = 1;
|
|
TextureDesc.MipLevels = 1;
|
|
TextureDesc.Format = UE::DXGIUtilities::GetSwapChainFormat(PixelFormat);
|
|
TextureDesc.SampleDesc.Count = 1;
|
|
TextureDesc.SampleDesc.Quality = 0;
|
|
TextureDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
|
|
TextureDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
|
|
|
|
Parent->GetDevice()->CreateCommittedResource(&HeapProps, D3D12_HEAP_FLAG_NONE, &TextureDesc, D3D12_RESOURCE_STATE_PRESENT, nullptr, IID_PPV_ARGS(BackBufferResource.GetInitReference()));
|
|
}
|
|
|
|
FD3D12ResourceDesc BackBufferDesc = BackBufferResource->GetDesc();
|
|
BackBufferDesc.bBackBuffer = true;
|
|
|
|
FString Name = FString::Printf(TEXT("BackBuffer%d"), BackBufferIndex);
|
|
|
|
ETextureCreateFlags SwapchainTextureCreateFlags = ETextureCreateFlags::RenderTargetable | ETextureCreateFlags::Presentable | ETextureCreateFlags::ResolveTargetable;
|
|
|
|
#if D3D12RHI_SUPPORTS_UAV_BACKBUFFER
|
|
SwapchainTextureCreateFlags |= ETextureCreateFlags::UAV;
|
|
#endif
|
|
|
|
bool const bQuadBufferStereo = FD3D12DynamicRHI::GetD3DRHI()->IsQuadBufferStereoEnabled();
|
|
|
|
FRHITextureCreateDesc CreateDesc = bQuadBufferStereo
|
|
? FRHITextureCreateDesc::Create2DArray(*Name)
|
|
: FRHITextureCreateDesc::Create2D(*Name);
|
|
|
|
CreateDesc
|
|
.SetExtent(FIntPoint((uint32)BackBufferDesc.Width, BackBufferDesc.Height))
|
|
.SetFormat(PixelFormat)
|
|
.SetFlags(SwapchainTextureCreateFlags)
|
|
.SetInitialState(ERHIAccess::Present);
|
|
|
|
if (bQuadBufferStereo)
|
|
{
|
|
CreateDesc.SetArraySize(2);
|
|
}
|
|
|
|
FD3D12DynamicRHI* DynamicRHI = FD3D12DynamicRHI::GetD3DRHI();
|
|
|
|
FD3D12Texture* SwapChainTexture = Adapter->CreateLinkedObject<FD3D12Texture>(FRHIGPUMask::All(), [&](FD3D12Device* Device, FD3D12Texture* FirstLinkedObject)
|
|
{
|
|
FD3D12Texture* NewTexture = DynamicRHI->CreateNewD3D12Texture(CreateDesc, Device);
|
|
|
|
const D3D12_RESOURCE_STATES InitialState = D3D12_RESOURCE_STATE_COMMON;
|
|
|
|
if (Device->GetGPUIndex() == Parent->GetGPUIndex())
|
|
{
|
|
FD3D12Resource* NewResourceWrapper = new FD3D12Resource(Device, FRHIGPUMask::All(), BackBufferResource, InitialState, BackBufferDesc);
|
|
NewResourceWrapper->AddRef();
|
|
NewTexture->ResourceLocation.AsStandAlone(NewResourceWrapper);
|
|
}
|
|
else // If this is not the GPU which will hold the back buffer, create a compatible texture so that it can still render to the viewport.
|
|
{
|
|
FClearValueBinding ClearValueBinding;
|
|
SafeCreateTexture2D(Device,
|
|
Adapter,
|
|
BackBufferDesc,
|
|
nullptr, // &ClearValueBinding,
|
|
&NewTexture->ResourceLocation,
|
|
NewTexture,
|
|
PixelFormat,
|
|
TexCreate_RenderTargetable | TexCreate_ShaderResource | TexCreate_Presentable,
|
|
InitialState,
|
|
*Name);
|
|
}
|
|
|
|
// active stereoscopy initialization
|
|
if (FD3D12DynamicRHI::GetD3DRHI()->IsQuadBufferStereoEnabled())
|
|
{
|
|
// left
|
|
D3D12_RENDER_TARGET_VIEW_DESC RTVDescLeft = {};
|
|
RTVDescLeft.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
|
|
RTVDescLeft.Format = BackBufferDesc.Format;
|
|
RTVDescLeft.Texture2DArray.MipSlice = 0;
|
|
RTVDescLeft.Texture2DArray.FirstArraySlice = 0;
|
|
RTVDescLeft.Texture2DArray.ArraySize = 1;
|
|
|
|
// right
|
|
D3D12_RENDER_TARGET_VIEW_DESC RTVDescRight = {};
|
|
RTVDescRight.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
|
|
RTVDescRight.Format = BackBufferDesc.Format;
|
|
RTVDescRight.Texture2DArray.MipSlice = 0;
|
|
RTVDescRight.Texture2DArray.FirstArraySlice = 1;
|
|
RTVDescRight.Texture2DArray.ArraySize = 1;
|
|
|
|
NewTexture->SetNumRTVs(2);
|
|
NewTexture->EmplaceRTV(RTVDescLeft, 0, FirstLinkedObject);
|
|
NewTexture->EmplaceRTV(RTVDescRight, 1, FirstLinkedObject);
|
|
}
|
|
else
|
|
{
|
|
// create the render target view
|
|
D3D12_RENDER_TARGET_VIEW_DESC RTVDesc = {};
|
|
RTVDesc.Format = BackBufferDesc.Format;
|
|
RTVDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
|
|
RTVDesc.Texture2D.MipSlice = 0;
|
|
|
|
NewTexture->SetNumRTVs(1);
|
|
NewTexture->EmplaceRTV(RTVDesc, 0, FirstLinkedObject);
|
|
}
|
|
|
|
// create a shader resource view to allow using the backbuffer as a texture
|
|
D3D12_SHADER_RESOURCE_VIEW_DESC SRVDesc = {};
|
|
SRVDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
|
SRVDesc.Format = BackBufferDesc.Format;
|
|
SRVDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
|
|
SRVDesc.Texture2D.MostDetailedMip = 0;
|
|
SRVDesc.Texture2D.MipLevels = 1;
|
|
|
|
NewTexture->EmplaceSRV(SRVDesc, FirstLinkedObject);
|
|
|
|
return NewTexture;
|
|
});
|
|
|
|
SetD3D12ResourceName(SwapChainTexture->GetResource(), *Name);
|
|
|
|
const D3D12_RESOURCE_ALLOCATION_INFO AllocationInfo = Parent->GetDevice()->GetResourceAllocationInfo(0, 1, &SwapChainTexture->GetResource()->GetDesc());
|
|
SwapChainTexture->ResourceLocation.SetSize(AllocationInfo.SizeInBytes);
|
|
|
|
FD3D12TextureStats::D3D12TextureAllocated(*SwapChainTexture);
|
|
return SwapChainTexture;
|
|
}
|
|
|
|
FD3D12Viewport::~FD3D12Viewport()
|
|
{
|
|
check(IsInRHIThread() || IsInRenderingThread());
|
|
|
|
#if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
// If the swap chain was in fullscreen mode, switch back to windowed before releasing the swap chain.
|
|
// DXGI throws an error otherwise.
|
|
if (SwapChain1)
|
|
{
|
|
SwapChain1->SetFullscreenState(0, nullptr);
|
|
}
|
|
#endif // D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
|
|
GetParentAdapter()->GetViewports().Remove(this);
|
|
|
|
FinalDestroyInternal();
|
|
}
|
|
|
|
#if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
inline DXGI_MODE_DESC SetupDXGI_MODE_DESC(uint32 SizeX, uint32 SizeY, EPixelFormat PixelFormat)
|
|
{
|
|
DXGI_MODE_DESC Ret{};
|
|
Ret.Width = SizeX;
|
|
Ret.Height = SizeY;
|
|
Ret.Format = UE::DXGIUtilities::GetSwapChainFormat(PixelFormat);
|
|
return Ret;
|
|
}
|
|
#endif
|
|
|
|
void FD3D12Viewport::InitializeBackBufferArrays()
|
|
{
|
|
#if WITH_MGPU
|
|
// This is a temporary helper to visualize what each GPU is rendering.
|
|
// Not specifying a value will cycle swap chain through all GPUs.
|
|
BackbufferMultiGPUBinding = 0;
|
|
if (GNumExplicitGPUsForRendering > 1)
|
|
{
|
|
if (FParse::Value(FCommandLine::Get(), TEXT("PresentGPU="), BackbufferMultiGPUBinding))
|
|
{
|
|
BackbufferMultiGPUBinding = FMath::Clamp<int32>(BackbufferMultiGPUBinding, INDEX_NONE, (int32)GNumExplicitGPUsForRendering - 1) ;
|
|
}
|
|
}
|
|
#endif // WITH_MGPU
|
|
|
|
for (FBackBufferData& Data : BackBuffers)
|
|
{
|
|
Data = FBackBufferData();
|
|
}
|
|
}
|
|
|
|
template<typename TResource, typename TSubresource = TResource>
|
|
bool ReleaseBackBufferResource(TRefCountPtr<TResource>& Resource, const TCHAR* ErrorName, uint32 Index)
|
|
{
|
|
const bool bValidReference = IsValidRef(Resource);
|
|
if (bValidReference)
|
|
{
|
|
// Tell the back buffer to delete immediately so that we can call resize.
|
|
if (Resource->GetRefCount() != 1)
|
|
{
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("%s %d leaking with %d refs during Resize."), ErrorName, Index, Resource->GetRefCount());
|
|
}
|
|
check(Resource->GetRefCount() == 1);
|
|
|
|
for (TSubresource& SubResource : *Resource)
|
|
{
|
|
SubResource.GetResource()->DoNotDeferDelete();
|
|
}
|
|
}
|
|
|
|
Resource.SafeRelease();
|
|
check(Resource == nullptr);
|
|
|
|
return bValidReference;
|
|
}
|
|
|
|
void FD3D12Viewport::Resize(uint32 InSizeX, uint32 InSizeY, bool bInIsFullscreen, EPixelFormat PreferredPixelFormat)
|
|
{
|
|
FD3D12Adapter* Adapter = GetParentAdapter();
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (RHIConsoleVariables::LogViewportEvents)
|
|
{
|
|
const FString& ThreadName = FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Thread %s: Resize Viewport, %s"), ThreadName.GetCharArray().GetData(), *GetStateString());
|
|
}
|
|
#endif
|
|
|
|
// Log relevant state changes, makes it easier to track/reproduce crashes
|
|
const bool bLogEvent =
|
|
(bIsFullscreen != bInIsFullscreen) ||
|
|
(bIsFullscreen && (InSizeX != SizeX || InSizeY != SizeY));
|
|
|
|
FString OldState;
|
|
if (bLogEvent)
|
|
{
|
|
OldState = GetStateString();
|
|
}
|
|
|
|
// Flush the outstanding GPU work and wait for it to complete.
|
|
FlushRenderingCommands();
|
|
Adapter->BlockUntilIdle();
|
|
|
|
// Unbind any dangling references to resources.
|
|
for (uint32 GPUIndex : FRHIGPUMask::All())
|
|
{
|
|
FD3D12Device* Device = Adapter->GetDevice(GPUIndex);
|
|
Device->GetDefaultCommandContext().ClearState(FD3D12ContextCommon::EClearStateMode::TransientOnly);
|
|
}
|
|
|
|
if (IsValidRef(CustomPresent))
|
|
{
|
|
CustomPresent->OnBackBufferResize();
|
|
}
|
|
|
|
#if D3D12RHI_SUPPORTS_UAV_BACKBUFFER
|
|
bool bWaitForBackBuffersUAVDelete = false;
|
|
|
|
// Release our backbuffer reference, as required by DXGI before calling ResizeBuffers.
|
|
for (uint32 Index = 0; Index < NumBackBuffers; Index++)
|
|
{
|
|
bWaitForBackBuffersUAVDelete |= ReleaseBackBufferResource<FD3D12UnorderedAccessView_RHI, FD3D12UnorderedAccessView>(BackBuffers[Index].UAV, TEXT("BackBuffer UAV"), Index);
|
|
}
|
|
|
|
if (bWaitForBackBuffersUAVDelete)
|
|
{
|
|
// The D3D12 UAV releases don't happen immediately, but are pushed to a delete queue processed on the RHI Thread. We need to ensure these are processed before releasing the swapchain buffers
|
|
// Calling FlushRenderingCommands is enough because it calls ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources) / ImmediateFlush(EImmediateFlushType::FlushRHIThread) internally
|
|
FlushRenderingCommands();
|
|
}
|
|
#endif
|
|
|
|
for (uint32 Index = 0; Index < NumBackBuffers; Index++)
|
|
{
|
|
ReleaseBackBufferResource(BackBuffers[Index].Texture, TEXT("BackBuffer"), Index);
|
|
#if D3D12RHI_USE_SDR_BACKBUFFER
|
|
ReleaseBackBufferResource(BackBuffers[Index].TextureSDR, TEXT("SDR BackBuffer"), Index);
|
|
#endif
|
|
}
|
|
|
|
ClearPresentQueue();
|
|
|
|
// Flush the outstanding GPU work and wait for it to complete.
|
|
FlushRenderingCommands();
|
|
Adapter->BlockUntilIdle();
|
|
|
|
// Keep the current pixel format if one wasn't specified.
|
|
if (PreferredPixelFormat == PF_Unknown)
|
|
{
|
|
PreferredPixelFormat = PixelFormat;
|
|
}
|
|
|
|
// Reset the full screen lost because we are resizing and handling fullscreen state change and full recreation of back buffers already
|
|
// We don't want to call resize again, which could happen during ConditionalResetSwapChain otherwise
|
|
bFullscreenLost = false;
|
|
|
|
if (SizeX != InSizeX || SizeY != InSizeY || PixelFormat != PreferredPixelFormat)
|
|
{
|
|
SizeX = InSizeX;
|
|
SizeY = InSizeY;
|
|
PixelFormat = PreferredPixelFormat;
|
|
|
|
check(SizeX > 0);
|
|
check(SizeY > 0);
|
|
#if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
if (bNeedSwapChain)
|
|
{
|
|
if (bInIsFullscreen)
|
|
{
|
|
const DXGI_MODE_DESC BufferDesc = SetupDXGI_MODE_DESC(SizeX, SizeY, PixelFormat);
|
|
if (FAILED(SwapChain1->ResizeTarget(&BufferDesc)))
|
|
{
|
|
ConditionalResetSwapChain(true);
|
|
}
|
|
}
|
|
}
|
|
#endif // D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
}
|
|
|
|
if (bIsFullscreen != bInIsFullscreen)
|
|
{
|
|
bIsFullscreen = bInIsFullscreen;
|
|
bIsValid = false;
|
|
|
|
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.
|
|
ConditionalResetSwapChain(true);
|
|
|
|
#if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
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 (in ResizeInternal)
|
|
// * 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(SizeX, SizeY, PixelFormat);
|
|
SwapChain1->ResizeTarget(&BufferDesc);
|
|
}
|
|
#endif // D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
}
|
|
}
|
|
|
|
RECT WindowRect = {};
|
|
#if PLATFORM_WINDOWS
|
|
GetWindowRect(WindowHandle, &WindowRect);
|
|
#endif
|
|
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);
|
|
|
|
ResizeInternal();
|
|
|
|
// Enable HDR if desired.
|
|
if (bHDREnabled)
|
|
{
|
|
EnableHDR();
|
|
}
|
|
else
|
|
{
|
|
ShutdownHDR();
|
|
}
|
|
|
|
if (bLogEvent)
|
|
{
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Swapchain Resized:\n\tBefore: %s\n\tAfter: %s"), *OldState, *GetStateString());
|
|
}
|
|
}
|
|
|
|
#if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
void* FD3D12Viewport::GetNativeSwapChain() const
|
|
{
|
|
return SwapChain1;
|
|
}
|
|
#endif // #if D3D12_VIEWPORT_EXPOSES_SWAP_CHAIN
|
|
|
|
void* FD3D12Viewport::GetNativeBackBufferTexture() const
|
|
{
|
|
return GetBackBuffer_RHIThread()->GetResource();
|
|
}
|
|
|
|
void* FD3D12Viewport::GetNativeBackBufferRT() const
|
|
{
|
|
return GetBackBuffer_RHIThread()->GetRenderTargetView(0, 0);
|
|
}
|
|
|
|
void FD3D12Viewport::SetCustomPresent(FRHICustomPresent* InCustomPresent)
|
|
{
|
|
CustomPresent = InCustomPresent;
|
|
}
|
|
|
|
FRHICustomPresent* FD3D12Viewport::GetCustomPresent() const
|
|
{
|
|
return CustomPresent;
|
|
}
|
|
|
|
/** Update the expected next present GPU back buffer index from RenderThread point of view */
|
|
void FD3D12Viewport::AdvanceExpectedBackBufferIndex_RenderThread()
|
|
{
|
|
bool bNeedsNativePresent = IsValidRef(CustomPresent) ?
|
|
CustomPresent->NeedsNativePresent() || CustomPresent->NeedsAdvanceBackbuffer() : true;
|
|
|
|
if (bNeedsNativePresent && IsPresentAllowed())
|
|
{
|
|
#if WITH_MGPU
|
|
FScopeLock Lock(&ExpectedBackBufferIndexLock);
|
|
#endif
|
|
|
|
SetBackBufferIndex_RenderThread(ExpectedBackBufferIndex_RenderThread + 1);
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (RHIConsoleVariables::LogViewportEvents)
|
|
{
|
|
const FString ThreadName(FThreadManager::Get().GetThreadName(FPlatformTLS::GetCurrentThreadId()));
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Thread %s: Incrementing Expected RenderThread back buffer index of viewport: %#016llx to value: %u"), ThreadName.GetCharArray().GetData(), this, ExpectedBackBufferIndex_RenderThread);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
FString FD3D12Viewport::GetStateString()
|
|
{
|
|
return FString::Printf(
|
|
TEXT("Viewport=0x%p, Num=%d, Size=(%d,%d), PF=%d, DXGIFormat=0x%x, Fullscreen=%d, AllowTearing=%d"),
|
|
this,
|
|
NumBackBuffers, SizeX, SizeY, static_cast<int32>(PixelFormat),
|
|
static_cast<int32>(UE::DXGIUtilities::GetSwapChainFormat(PixelFormat)),
|
|
static_cast<int32>(bIsFullscreen),
|
|
static_cast<int32>(bAllowTearing)
|
|
);
|
|
}
|
|
|
|
static bool IsTransientPresentationError(HRESULT Result)
|
|
{
|
|
return Result == E_INVALIDARG ||
|
|
Result == DXGI_ERROR_INVALID_CALL;
|
|
}
|
|
|
|
/** Presents the swap chain checking the return result. */
|
|
bool FD3D12Viewport::PresentChecked(IRHICommandContext& RHICmdContext, int32 SyncInterval)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
// 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 (bIsValid && SwapChain1.IsValid())
|
|
{
|
|
// Check if the viewport's swap chain has been invalidated by DXGI.
|
|
BOOL bSwapChainFullscreenState;
|
|
TRefCountPtr<IDXGIOutput> SwapChainOutput;
|
|
SwapChain1->GetFullscreenState(&bSwapChainFullscreenState, SwapChainOutput.GetInitReference());
|
|
// Can't compare BOOL with bool...
|
|
if ( (!!bSwapChainFullscreenState) != bIsFullscreen )
|
|
{
|
|
bFullscreenLost = true;
|
|
bIsValid = false;
|
|
}
|
|
}
|
|
|
|
if (!bIsValid)
|
|
{
|
|
#if WITH_MGPU
|
|
// Present failed so current expected GPU index will not match anymore, so patch up expected back buffer index
|
|
// Warning: Present is skipped for this frame but could cause a black screen for the next frame as well
|
|
FScopeLock Lock(&ExpectedBackBufferIndexLock);
|
|
SetBackBufferIndex_RenderThread((ExpectedBackBufferIndex_RenderThread == 0) ? NumBackBuffers - 1 : ExpectedBackBufferIndex_RenderThread - 1);
|
|
#endif // WITH_MGPU
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool bNeedNativePresent = true;
|
|
if (IsValidRef(CustomPresent))
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_D3D12CustomPresentTime);
|
|
bNeedNativePresent = CustomPresent->Present(RHICmdContext, SyncInterval);
|
|
}
|
|
|
|
if (bNeedNativePresent)
|
|
{
|
|
static constexpr uint32 MaxPresentFailures = 5;
|
|
|
|
// Present the back buffer to the viewport window.
|
|
// In case presentation failures are transient, don't fault on the first one.
|
|
HRESULT Result = PresentInternal(SyncInterval);
|
|
if (SUCCEEDED(Result))
|
|
{
|
|
CheckedPresentFailureCounter = 0;
|
|
}
|
|
else if (!IsTransientPresentationError(Result) || ++CheckedPresentFailureCounter >= MaxPresentFailures)
|
|
{
|
|
VERIFYD3D12RESULT_LAMBDA(Result, GetParentAdapter()->GetD3DDevice(), [this]
|
|
{
|
|
return GetStateString();
|
|
});
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(
|
|
LogD3D12RHI, Error,
|
|
TEXT("Swapchain presentation try %u/%u failed with HR(0x%x): %s"),
|
|
CheckedPresentFailureCounter, MaxPresentFailures,
|
|
Result, *GetStateString()
|
|
);
|
|
}
|
|
|
|
if (IsValidRef(CustomPresent))
|
|
{
|
|
CustomPresent->PostPresent();
|
|
}
|
|
|
|
#if LOG_PRESENT
|
|
const FString& ThreadName = FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("*** PRESENT: Thread %s: Viewport %#016llx: BackBuffer %#016llx (SyncInterval %u) ***"), ThreadName.GetCharArray().GetData(), this, GetBackBuffer_RHIThread(), SyncInterval);
|
|
#endif
|
|
}
|
|
|
|
return bNeedNativePresent;
|
|
}
|
|
|
|
bool FD3D12Viewport::Present(FD3D12CommandContextBase& ContextBase, bool bLockToVsync)
|
|
{
|
|
if (!IsPresentAllowed())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
check(ContextBase.GetParentAdapter() == GetParentAdapter());
|
|
|
|
for (uint32 GPUIndex : FRHIGPUMask::All())
|
|
{
|
|
FD3D12CommandContext& Context = *ContextBase.GetSingleDeviceContext(GPUIndex);
|
|
|
|
// Those are not necessarily the swap chain back buffer in case of multi-gpu
|
|
FD3D12Texture* DeviceBackBuffer = Context.RetrieveObject<FD3D12Texture, FRHITexture>(GetBackBuffer_RHIThread());
|
|
|
|
Context.FlushResourceBarriers();
|
|
|
|
// Currently, the swap chain Present() is called directly by the RHI thread.
|
|
// We need to submit the above commands and wait for the submission thread to process everything before we can continue.
|
|
Context.FlushCommands(ED3D12FlushFlags::WaitForSubmission);
|
|
}
|
|
|
|
const int32 SyncInterval = bLockToVsync ? RHIGetSyncInterval() : 0;
|
|
const bool bNativelyPresented = PresentChecked(ContextBase, SyncInterval);
|
|
|
|
if (bNativelyPresented || (CustomPresent && CustomPresent->NeedsAdvanceBackbuffer()))
|
|
{
|
|
// Increment back buffer
|
|
#if DXGI_MAX_SWAPCHAIN_INTERFACE >= 3
|
|
if (bNativelyPresented && SwapChain3)
|
|
{
|
|
SetBackBufferIndex_RHIThread(SwapChain3->GetCurrentBackBufferIndex());
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
SetBackBufferIndex_RHIThread(CurrentBackBufferIndex_RHIThread + 1);
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (RHIConsoleVariables::LogViewportEvents)
|
|
{
|
|
const FString& ThreadName = FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Thread %s: Incrementing RHIThread back buffer index of viewport: %#016llx to value: %u BackBuffer %#016llx"), ThreadName.GetCharArray().GetData(), this, CurrentBackBufferIndex_RHIThread, CurrentBackBuffer_RHIThread->Texture.GetReference());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return bNativelyPresented;
|
|
}
|
|
|
|
void FD3D12Viewport::WaitForFrameEventCompletion()
|
|
{
|
|
if (FrameSyncPoints.Num())
|
|
{
|
|
for (auto& SyncPoint : FrameSyncPoints)
|
|
{
|
|
if (SyncPoint)
|
|
{
|
|
SyncPoint->Wait();
|
|
}
|
|
}
|
|
|
|
FrameSyncPoints.Reset();
|
|
}
|
|
}
|
|
|
|
void FD3D12Viewport::IssueFrameEvent()
|
|
{
|
|
TArray<FD3D12Payload*> Payloads;
|
|
for (FD3D12Device* Device : ParentAdapter->GetDevices())
|
|
{
|
|
FD3D12CommandContext& Context = Device->GetDefaultCommandContext();
|
|
|
|
FD3D12SyncPointRef SyncPoint = FD3D12SyncPoint::Create(ED3D12SyncPointType::GPUAndCPU);
|
|
|
|
Context.SignalSyncPoint(SyncPoint);
|
|
Context.Finalize(Payloads);
|
|
|
|
FrameSyncPoints.Emplace(MoveTemp(SyncPoint));
|
|
}
|
|
|
|
FD3D12DynamicRHI::GetD3DRHI()->SubmitPayloads(MoveTemp(Payloads));
|
|
}
|
|
|
|
bool FD3D12Viewport::CheckHDRSupport()
|
|
{
|
|
return IsHDREnabled();
|
|
}
|
|
|
|
EPixelFormat GetDefaultBackBufferPixelFormat()
|
|
{
|
|
static const auto CVarDefaultBackBufferPixelFormat = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.DefaultBackBufferPixelFormat"));
|
|
return EDefaultBackBufferPixelFormat::Convert2PixelFormat(EDefaultBackBufferPixelFormat::FromInt(CVarDefaultBackBufferPixelFormat->GetValueOnGameThread()));
|
|
}
|
|
|
|
/*==============================================================================
|
|
* The following RHI functions must be called from the main thread.
|
|
*=============================================================================*/
|
|
FViewportRHIRef FD3D12DynamicRHI::RHICreateViewport(void* WindowHandle, uint32 SizeX, uint32 SizeY, bool bIsFullscreen, EPixelFormat PreferredPixelFormat)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
if (PreferredPixelFormat == EPixelFormat::PF_Unknown)
|
|
{
|
|
PreferredPixelFormat = GetDefaultBackBufferPixelFormat();
|
|
}
|
|
|
|
FD3D12Viewport* RenderingViewport = new FD3D12Viewport(&GetAdapter(), (HWND)WindowHandle, SizeX, SizeY, bIsFullscreen, PreferredPixelFormat);
|
|
RenderingViewport->Init();
|
|
return RenderingViewport;
|
|
}
|
|
|
|
void FD3D12DynamicRHI::RHIResizeViewport(FRHIViewport* ViewportRHI, uint32 SizeX, uint32 SizeY, bool bIsFullscreen)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
FD3D12Viewport* Viewport = FD3D12DynamicRHI::ResourceCast(ViewportRHI);
|
|
Viewport->Resize(SizeX, SizeY, bIsFullscreen, PF_Unknown);
|
|
}
|
|
|
|
void FD3D12DynamicRHI::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()));
|
|
}
|
|
|
|
FD3D12Viewport* Viewport = FD3D12DynamicRHI::ResourceCast(ViewportRHI);
|
|
Viewport->Resize(SizeX, SizeY, bIsFullscreen, PreferredPixelFormat);
|
|
}
|
|
|
|
void FD3D12DynamicRHI::RHITick(float DeltaTime)
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
// Check if any swap chains have been invalidated.
|
|
auto& Viewports = GetAdapter().GetViewports();
|
|
for (int32 ViewportIndex = 0; ViewportIndex < Viewports.Num(); ViewportIndex++)
|
|
{
|
|
Viewports[ViewportIndex]->ConditionalResetSwapChain(false);
|
|
}
|
|
}
|
|
|
|
/*=============================================================================
|
|
* Viewport functions.
|
|
*=============================================================================*/
|
|
|
|
void FD3D12CommandContextBase::RHIBeginDrawingViewport(FRHIViewport* ViewportRHI, FRHITexture* RenderTargetRHI)
|
|
{
|
|
FD3D12Viewport* Viewport = FD3D12DynamicRHI::ResourceCast(ViewportRHI);
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_D3D12PresentTime);
|
|
|
|
// Set the viewport.
|
|
check(!ParentAdapter->GetDrawingViewport());
|
|
ParentAdapter->SetDrawingViewport(Viewport);
|
|
|
|
if (RenderTargetRHI == nullptr)
|
|
{
|
|
RenderTargetRHI = Viewport->GetBackBuffer_RHIThread();
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (RHIConsoleVariables::LogViewportEvents)
|
|
{
|
|
const FString& ThreadName = FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Thread %s: RHIBeginDrawingViewport (Viewport %#016llx: BackBuffer %#016llx: CmdList: %016llx)"),
|
|
ThreadName.GetCharArray().GetData(),
|
|
Viewport,
|
|
RenderTargetRHI,
|
|
GetSingleDeviceContext(0)->BaseCommandList().GetNoRefCount()
|
|
);
|
|
}
|
|
#endif
|
|
|
|
FRHICustomPresent* CustomPresent = Viewport->GetCustomPresent();
|
|
if (CustomPresent)
|
|
{
|
|
CustomPresent->BeginDrawing();
|
|
}
|
|
}
|
|
|
|
void FD3D12CommandContextBase::RHIEndDrawingViewport(FRHIViewport* ViewportRHI, bool bPresent, bool bLockToVsync)
|
|
{
|
|
FD3D12Viewport* Viewport = FD3D12DynamicRHI::ResourceCast(ViewportRHI);
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (RHIConsoleVariables::LogViewportEvents)
|
|
{
|
|
const FString& ThreadName = FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Thread %s: RHIEndDrawingViewport (Viewport %#016llx: BackBuffer %#016llx: CmdList: %016llx)"),
|
|
ThreadName.GetCharArray().GetData(),
|
|
Viewport,
|
|
Viewport->GetBackBuffer_RHIThread(),
|
|
GetSingleDeviceContext(0)->BaseCommandList().GetNoRefCount()
|
|
);
|
|
}
|
|
#endif
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_D3D12PresentTime);
|
|
|
|
check(ParentAdapter->GetDrawingViewport() == Viewport);
|
|
ParentAdapter->SetDrawingViewport(nullptr);
|
|
|
|
bool bNativelyPresented = true;
|
|
if (bPresent)
|
|
{
|
|
bNativelyPresented = Viewport->Present(*this, bLockToVsync);
|
|
}
|
|
|
|
// Multi-GPU support : here each GPU wait's for it's own frame completion.
|
|
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 FD3D12DynamicRHI::RHIAdvanceFrameForGetViewportBackBuffer(FRHIViewport* ViewportRHI)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
if (RHIConsoleVariables::LogViewportEvents)
|
|
{
|
|
const FString& ThreadName = FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Thread %s: RHIAdvanceFrameForGetViewportBackBuffer"), ThreadName.GetCharArray().GetData());
|
|
}
|
|
#endif
|
|
|
|
// Don't need to do anything on the back because dummy back buffer texture is used to make sure the correct back
|
|
// buffer index is always used on RHI thread
|
|
|
|
// But advance the expected present GPU index so the next call to RHIGetViewportNextPresentGPUIndex returns the expected GPU index for the next present.
|
|
// Warning: when present fails or is not called on the RHIThread then this might not be in sync but RHI thread will fix up the correct state
|
|
// Present doesn't happen so shouldn't matter that the index was wrong then
|
|
FD3D12Viewport* Viewport = FD3D12DynamicRHI::ResourceCast(ViewportRHI);
|
|
Viewport->AdvanceExpectedBackBufferIndex_RenderThread();
|
|
}
|
|
|
|
uint32 FD3D12DynamicRHI::RHIGetViewportNextPresentGPUIndex(FRHIViewport* ViewportRHI)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
#if WITH_MGPU
|
|
const FD3D12Viewport* Viewport = FD3D12DynamicRHI::ResourceCast(ViewportRHI);
|
|
if (Viewport)
|
|
{
|
|
return Viewport->GetNextPresentGPUIndex();
|
|
}
|
|
#endif // WITH_MGPU
|
|
|
|
return 0;
|
|
}
|
|
|
|
FTextureRHIRef FD3D12DynamicRHI::RHIGetViewportBackBuffer(FRHIViewport* ViewportRHI)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
const FD3D12Viewport* const Viewport = FD3D12DynamicRHI::ResourceCast(ViewportRHI);
|
|
|
|
FRHITexture* SelectedBackBuffer = Viewport->GetBackBuffer_RenderThread();
|
|
#if !UE_BUILD_SHIPPING
|
|
if (RHIConsoleVariables::LogViewportEvents)
|
|
{
|
|
const FString& ThreadName = FThreadManager::GetThreadName(FPlatformTLS::GetCurrentThreadId());
|
|
UE_LOG(LogD3D12RHI, Log, TEXT("Thread %s: RHIGetViewportBackBuffer (Viewport %#016llx: BackBuffer %#016llx)"), ThreadName.GetCharArray().GetData(), Viewport, SelectedBackBuffer);
|
|
}
|
|
#endif
|
|
|
|
return SelectedBackBuffer;
|
|
}
|