// 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 #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 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 BackBufferRenderTargetView; TRefCountPtr 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 > 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 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 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(CPUTime, GPUTime); if (FrameTime >= RHIConsoleVariables::SyncRefreshThreshold * RefreshPeriod) { SyncCounter--; } else if (bValidGPUTime) { SyncCounter++; } SyncCounter = FMath::Clamp(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 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 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