From afacd8014c57c8018f98e604cac58687db7fe182 Mon Sep 17 00:00:00 2001 From: Stephen Saunders Date: Thu, 17 Nov 2022 23:42:06 -0500 Subject: [PATCH] Improve timer stats, get / set displayFrequency, implement Vulkan vsync mode 1, fix DX12 vsync mode 0 for fullscreen --- neo/framework/Console.cpp | 34 +++++++++--------- neo/renderer/RenderBackend.cpp | 2 +- neo/sys/DeviceManager_DX12.cpp | 15 +++----- neo/sys/DeviceManager_VK.cpp | 25 ++++++++++--- neo/sys/sdl/sdl_vkimp.cpp | 44 +++++++++++++++++++++-- neo/sys/win32/win_glimp.cpp | 66 ++++++++++++++++++++++++---------- 6 files changed, 131 insertions(+), 55 deletions(-) diff --git a/neo/framework/Console.cpp b/neo/framework/Console.cpp index 1e856cbf..6a39ce64 100644 --- a/neo/framework/Console.cpp +++ b/neo/framework/Console.cpp @@ -220,7 +220,7 @@ extern bool R_UseTemporalAA(); #define FPS_FRAMES_HISTORY 90 float idConsoleLocal::DrawFPS( float y ) { - extern idCVar com_smp; + extern idCVar r_swapInterval; static float previousTimes[FPS_FRAMES]; static float previousTimesNormalized[FPS_FRAMES_HISTORY]; @@ -288,8 +288,6 @@ float idConsoleLocal::DrawFPS( float y ) const uint64 rendererBackEndTime = commonLocal.GetRendererBackEndMicroseconds(); const uint64 rendererShadowsTime = commonLocal.GetRendererShadowsMicroseconds(); - // SRS - GPU idle time calculation depends on whether game is operating in smp mode or not - const uint64 rendererGPUIdleTime = commonLocal.GetRendererIdleMicroseconds() - ( com_smp.GetInteger() > 0 && com_editors == 0 ? 0 : gameThreadTotalTime ); const uint64 rendererGPUTime = commonLocal.GetRendererGPUMicroseconds(); const uint64 rendererGPUEarlyZTime = commonLocal.GetRendererGpuEarlyZMicroseconds(); const uint64 rendererGPU_SSAOTime = commonLocal.GetRendererGpuSSAOMicroseconds(); @@ -300,15 +298,20 @@ float idConsoleLocal::DrawFPS( float y ) const uint64 rendererGPUShaderPassesTime = commonLocal.GetRendererGpuShaderPassMicroseconds(); const uint64 rendererGPU_TAATime = commonLocal.GetRendererGpuTAAMicroseconds(); const uint64 rendererGPUPostProcessingTime = commonLocal.GetRendererGpuPostProcessingMicroseconds(); - const int maxTime = int( 1000 / com_engineHz_latched ) * 1000; - // SRS - Get GPU sync time at the start of a frame (com_smp = 1 or 0) and at the end of a frame (com_smp = -1) - const uint64 rendererStartFrameSyncTime = commonLocal.GetRendererStartFrameSyncMicroseconds(); - const uint64 rendererEndFrameSyncTime = commonLocal.GetRendererEndFrameSyncMicroseconds(); + // SRS - Calculate max fps and max frame time based on glConfig.displayFrequency if vsync enabled and lower than engine Hz, otherwise use com_engineHz_latched + const int max_FPS = ( r_swapInterval.GetInteger() > 0 && glConfig.displayFrequency > 0 ? std::min( glConfig.displayFrequency, int( com_engineHz_latched ) ) : com_engineHz_latched ); + const int maxTime = 1000.0 / max_FPS * 1000; - // SRS - Total CPU and Frame time calculations depend on whether game is operating in smp mode or not - const uint64 totalCPUTime = ( com_smp.GetInteger() > 0 && com_editors == 0 ? std::max( gameThreadTotalTime, rendererBackEndTime ) : gameThreadTotalTime + rendererBackEndTime ); - const uint64 totalFrameTime = ( com_smp.GetInteger() > 0 && com_editors == 0 ? std::max( gameThreadTotalTime, rendererEndFrameSyncTime ) : gameThreadTotalTime + rendererEndFrameSyncTime ) + rendererStartFrameSyncTime; + // SRS - Frame idle and busy time calculations are based on direct frame-over-frame measurement relative to finishSyncTime + const uint64 frameIdleTime = commonLocal.mainFrameTiming.startGameTime - commonLocal.mainFrameTiming.finishSyncTime; + const uint64 frameBusyTime = commonLocal.frameTiming.finishSyncTime - commonLocal.mainFrameTiming.startGameTime; + + // SRS - Frame sync time represents swap buffer synchronization + game thread wait + other time spent outside of rendering + const uint64 frameSyncTime = commonLocal.frameTiming.finishSyncTime - commonLocal.mainFrameTiming.finishRenderTime; + + // SRS - GPU idle time is simply the difference between measured frame-over-frame time and GPU busy time (directly from GPU timers) + const uint64 rendererGPUIdleTime = frameBusyTime + frameIdleTime - rendererGPUTime; #if 1 @@ -476,7 +479,7 @@ float idConsoleLocal::DrawFPS( float y ) } else { - ImGui::TextColored( fps < com_engineHz_latched ? colorRed : colorYellow, "Average FPS %i", fps ); + ImGui::TextColored( fps < max_FPS ? colorRed : colorYellow, "Average FPS %i", fps ); } ImGui::Spacing(); @@ -486,14 +489,13 @@ float idConsoleLocal::DrawFPS( float y ) ImGui::TextColored( gameThreadGameTime > maxTime ? colorRed : colorWhite, "Game: %5llu us SSAO: %5llu us", gameThreadGameTime, rendererGPU_SSAOTime ); ImGui::TextColored( gameThreadRenderTime > maxTime ? colorRed : colorWhite, "RF: %5llu us SSR: %5llu us", gameThreadRenderTime, rendererGPU_SSRTime ); ImGui::TextColored( rendererBackEndTime > maxTime ? colorRed : colorWhite, "RB: %5llu us Ambient Pass: %5llu us", rendererBackEndTime, rendererGPUAmbientPassTime ); - ImGui::TextColored( rendererGPUShadowAtlasTime > maxTime ? colorRed : colorWhite, " Shadow Atlas: %5llu us", rendererGPUShadowAtlasTime ); - ImGui::TextColored( rendererShadowsTime > maxTime ? colorRed : colorWhite, "Shadows: %5llu us Interactions: %5llu us", rendererShadowsTime, rendererGPUInteractionsTime ); + ImGui::TextColored( rendererGPUShadowAtlasTime > maxTime ? colorRed : colorWhite, "Shadows: %5llu us Shadow Atlas: %5llu us", rendererShadowsTime, rendererGPUShadowAtlasTime ); + ImGui::TextColored( rendererGPUInteractionsTime > maxTime ? colorRed : colorWhite, "Sync: %5llu us Interactions: %5llu us", frameSyncTime, rendererGPUInteractionsTime ); ImGui::TextColored( rendererGPUShaderPassesTime > maxTime ? colorRed : colorWhite, " Shader Pass: %5llu us", rendererGPUShaderPassesTime ); ImGui::TextColored( rendererGPU_TAATime > maxTime ? colorRed : colorWhite, " TAA: %5llu us", rendererGPU_TAATime ); ImGui::TextColored( rendererGPUPostProcessingTime > maxTime ? colorRed : colorWhite, " PostFX: %5llu us", rendererGPUPostProcessingTime ); - ImGui::TextColored( totalCPUTime > maxTime || rendererGPUTime > maxTime ? colorRed : colorWhite, - "Total: %5llu us Total: %5llu us", totalCPUTime, rendererGPUTime ); - ImGui::TextColored( totalFrameTime > maxTime ? colorRed : colorWhite, "Frame: %5llu us Idle: %5llu us", totalFrameTime, rendererGPUIdleTime ); + ImGui::TextColored( frameBusyTime > maxTime || rendererGPUTime > maxTime ? colorRed : colorWhite, "Total: %5llu us Total: %5llu us", frameBusyTime, rendererGPUTime ); + ImGui::TextColored( colorWhite, "Idle: %5llu us Idle: %5llu us", frameIdleTime, rendererGPUIdleTime ); ImGui::End(); } diff --git a/neo/renderer/RenderBackend.cpp b/neo/renderer/RenderBackend.cpp index 856115e1..d7daffb4 100644 --- a/neo/renderer/RenderBackend.cpp +++ b/neo/renderer/RenderBackend.cpp @@ -6664,7 +6664,7 @@ void idRenderBackend::ExecuteBackEndCommands( const emptyCommand_t* cmds ) // SRS - Restore timestamp capture state after overlay GUI rendering is finished glConfig.timerQueryAvailable = timerQueryAvailable; renderLog.CloseBlock(); - renderLog.CloseMainBlock(); + renderLog.CloseMainBlock( MRB_DRAW_GUI ); } else { diff --git a/neo/sys/DeviceManager_DX12.cpp b/neo/sys/DeviceManager_DX12.cpp index fa4166b3..9438d3cd 100644 --- a/neo/sys/DeviceManager_DX12.cpp +++ b/neo/sys/DeviceManager_DX12.cpp @@ -613,21 +613,14 @@ void DeviceManager_DX12::Present() UINT presentFlags = 0; - if( r_swapInterval.GetInteger() == 1 ) - { - SetVsyncEnabled( false ); - } - else if( r_swapInterval.GetInteger() == 2 ) - { - SetVsyncEnabled( true ); - } - - if( !deviceParms.vsyncEnabled && !glConfig.isFullscreen && m_TearingSupported && r_swapInterval.GetInteger() == 0 ) + // SRS - DXGI docs say fullscreen must be disabled for unlocked fps/tear, but this does not seem to be true + if( !deviceParms.vsyncEnabled && m_TearingSupported ) //&& !glConfig.isFullscreen ) { presentFlags |= DXGI_PRESENT_ALLOW_TEARING; } - m_SwapChain->Present( deviceParms.vsyncEnabled ? 1 : 0, presentFlags ); + // SRS - Don't change deviceParms.vsyncEnabled here, simply test for vsync mode 2 to set DXGI SyncInterval + m_SwapChain->Present( deviceParms.vsyncEnabled && r_swapInterval.GetInteger() == 2 ? 1 : 0, presentFlags ); m_FrameFence->SetEventOnCompletion( m_FrameCount, m_FrameFenceEvents[bufferIndex] ); m_GraphicsQueue->Signal( m_FrameFence, m_FrameCount ); diff --git a/neo/sys/DeviceManager_VK.cpp b/neo/sys/DeviceManager_VK.cpp index 483e4657..43961f4f 100644 --- a/neo/sys/DeviceManager_VK.cpp +++ b/neo/sys/DeviceManager_VK.cpp @@ -258,6 +258,9 @@ private: std::queue m_FramesInFlight; std::vector m_QueryPool; + // SRS - flag indicating support for eFifoRelaxed surface presentation (r_swapInterval = 1) mode + bool enablePModeFifoRelaxed = false; + private: static VKAPI_ATTR VkBool32 VKAPI_CALL vulkanDebugCallback( @@ -571,6 +574,14 @@ bool DeviceManager_VK::pickPhysicalDevice() deviceIsGood = false; } + if( ( find( surfacePModes.begin(), surfacePModes.end(), vk::PresentModeKHR::eImmediate ) == surfacePModes.end() ) || + ( find( surfacePModes.begin(), surfacePModes.end(), vk::PresentModeKHR::eFifo ) == surfacePModes.end() ) ) + { + // can't find the required surface present modes + errorStream << std::endl << " - does not support the requested surface present modes"; + deviceIsGood = false; + } + if( !findQueueFamilies( dev, m_WindowSurface ) ) { // device doesn't have all the queue families we need @@ -865,13 +876,17 @@ bool DeviceManager_VK::createDevice() // SRS - Determine if preferred image depth/stencil format D24S8 is supported (issue with Vulkan on AMD GPUs) vk::ImageFormatProperties imageFormatProperties; - const vk::Result ret = m_VulkanPhysicalDevice.getImageFormatProperties( vk::Format( VK_FORMAT_D24_UNORM_S8_UINT ), - vk::ImageType( VK_IMAGE_TYPE_2D ), - vk::ImageTiling( VK_IMAGE_TILING_OPTIMAL ), - vk::ImageUsageFlags( VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT ), + const vk::Result ret = m_VulkanPhysicalDevice.getImageFormatProperties( vk::Format::eD24UnormS8Uint, + vk::ImageType::e2D, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlags( vk::ImageUsageFlagBits::eDepthStencilAttachment ), vk::ImageCreateFlags( 0 ), &imageFormatProperties ); deviceParms.enableImageFormatD24S8 = ( ret == vk::Result::eSuccess ); + + // SRS - Determine if "smart" (r_swapInterval = 1) vsync mode eFifoRelaxed is supported by device and surface + auto surfacePModes = m_VulkanPhysicalDevice.getSurfacePresentModesKHR( m_WindowSurface ); + enablePModeFifoRelaxed = find( surfacePModes.begin(), surfacePModes.end(), vk::PresentModeKHR::eFifoRelaxed ) != surfacePModes.end(); // stash the renderer string auto prop = m_VulkanPhysicalDevice.getProperties(); @@ -971,7 +986,7 @@ bool DeviceManager_VK::createSwapChain() .setPQueueFamilyIndices( enableSwapChainSharing ? queues.data() : nullptr ) .setPreTransform( vk::SurfaceTransformFlagBitsKHR::eIdentity ) .setCompositeAlpha( vk::CompositeAlphaFlagBitsKHR::eOpaque ) - .setPresentMode( deviceParms.vsyncEnabled ? vk::PresentModeKHR::eFifo : vk::PresentModeKHR::eImmediate ) + .setPresentMode( deviceParms.vsyncEnabled ? ( r_swapInterval.GetInteger() == 2 || !enablePModeFifoRelaxed ? vk::PresentModeKHR::eFifo : vk::PresentModeKHR::eFifoRelaxed ) : vk::PresentModeKHR::eImmediate ) .setClipped( true ) .setOldSwapchain( nullptr ); diff --git a/neo/sys/sdl/sdl_vkimp.cpp b/neo/sys/sdl/sdl_vkimp.cpp index f4c5c40a..cbf2c8e4 100644 --- a/neo/sys/sdl/sdl_vkimp.cpp +++ b/neo/sys/sdl/sdl_vkimp.cpp @@ -188,6 +188,18 @@ void VKimp_PreInit() // DG: added this function for SDL compatibility } } +// SRS - Function to get display frequency of monitor hosting the current window +static int GetDisplayFrequency( glimpParms_t parms ) +{ + SDL_DisplayMode m = {0}; + if( SDL_GetWindowDisplayMode( window, &m ) < 0 ) + { + common->Warning( "Couldn't get display refresh rate, reason: %s", SDL_GetError() ); + return parms.displayHz; + } + + return m.refresh_rate; +} /* Eric: Is the majority of this function not needed since switching from GL to Vulkan? =================== @@ -344,6 +356,19 @@ bool VKimp_Init( glimpParms_t parms ) continue; } + // SRS - Make sure display is set to requested refresh rate from the start + if( parms.displayHz > 0 && parms.displayHz != GetDisplayFrequency( parms ) ) + { + SDL_DisplayMode m = {0}; + SDL_GetWindowDisplayMode( window, &m ); + + m.refresh_rate = parms.displayHz; + if( SDL_SetWindowDisplayMode( window, &m ) < 0 ) + { + common->Warning( "Couldn't set display refresh rate to %i Hz", parms.displayHz ); + } + } + // RB begin SDL_GetWindowSize( window, &glConfig.nativeScreenWidth, &glConfig.nativeScreenHeight ); // RB end @@ -360,7 +385,7 @@ bool VKimp_Init( glimpParms_t parms ) #endif // RB begin - glConfig.displayFrequency = 60; // FIXME: should use parms.displayHz and set mode correctly from start + glConfig.displayFrequency = GetDisplayFrequency( parms ); glConfig.isStereoPixelFormat = parms.stereo; glConfig.multisamples = parms.multiSamples; @@ -474,8 +499,10 @@ static bool SetScreenParmsFullscreen( glimpParms_t parms ) static bool SetScreenParmsWindowed( glimpParms_t parms ) { - SDL_SetWindowSize( window, parms.width, parms.height ); + // SRS - handle differences in WM behaviour: for macOS set position first, for linux set it last +#if defined(__APPLE__) SDL_SetWindowPosition( window, parms.x, parms.y ); +#endif // if we're currently in fullscreen mode, we need to disable that if( SDL_GetWindowFlags( window ) & SDL_WINDOW_FULLSCREEN ) @@ -486,6 +513,17 @@ static bool SetScreenParmsWindowed( glimpParms_t parms ) return false; } } + + SDL_SetWindowSize( window, parms.width, parms.height ); + + // SRS - this logic prevents window position drift on linux when coming in and out of fullscreen +#if defined(__linux__) + SDL_bool borderState = SDL_GetWindowFlags( window ) & SDL_WINDOW_BORDERLESS ? SDL_FALSE : SDL_TRUE; + SDL_SetWindowBordered( window, SDL_FALSE ); + SDL_SetWindowPosition( window, parms.x, parms.y ); + SDL_SetWindowBordered( window, borderState ); +#endif + return true; } @@ -522,7 +560,7 @@ bool VKimp_SetScreenParms( glimpParms_t parms ) glConfig.isStereoPixelFormat = parms.stereo; glConfig.nativeScreenWidth = parms.width; glConfig.nativeScreenHeight = parms.height; - glConfig.displayFrequency = parms.displayHz; + glConfig.displayFrequency = GetDisplayFrequency( parms ); glConfig.multisamples = parms.multiSamples; return true; diff --git a/neo/sys/win32/win_glimp.cpp b/neo/sys/win32/win_glimp.cpp index abde471c..6bd73b29 100644 --- a/neo/sys/win32/win_glimp.cpp +++ b/neo/sys/win32/win_glimp.cpp @@ -694,6 +694,8 @@ GetDisplayCoordinates */ static bool GetDisplayCoordinates( const int deviceNum, int& x, int& y, int& width, int& height, int& displayHz ) { + bool verbose = false; + idStr deviceName = GetDeviceName( deviceNum ); if( deviceName.Length() == 0 ) { @@ -729,24 +731,27 @@ static bool GetDisplayCoordinates( const int deviceNum, int& x, int& y, int& wid return false; } - common->Printf( "display device: %i\n", deviceNum ); - common->Printf( " DeviceName : %s\n", device.DeviceName ); - common->Printf( " DeviceString: %s\n", device.DeviceString ); - common->Printf( " StateFlags : 0x%x\n", device.StateFlags ); - common->Printf( " DeviceID : %s\n", device.DeviceID ); - common->Printf( " DeviceKey : %s\n", device.DeviceKey ); - common->Printf( " DeviceName : %s\n", monitor.DeviceName ); - common->Printf( " DeviceString: %s\n", monitor.DeviceString ); - common->Printf( " StateFlags : 0x%x\n", monitor.StateFlags ); - common->Printf( " DeviceID : %s\n", monitor.DeviceID ); - common->Printf( " DeviceKey : %s\n", monitor.DeviceKey ); - common->Printf( " dmPosition.x : %i\n", devmode.dmPosition.x ); - common->Printf( " dmPosition.y : %i\n", devmode.dmPosition.y ); - common->Printf( " dmBitsPerPel : %i\n", devmode.dmBitsPerPel ); - common->Printf( " dmPelsWidth : %i\n", devmode.dmPelsWidth ); - common->Printf( " dmPelsHeight : %i\n", devmode.dmPelsHeight ); - common->Printf( " dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags ); - common->Printf( " dmDisplayFrequency: %i\n", devmode.dmDisplayFrequency ); + if( verbose ) + { + common->Printf("display device: %i\n", deviceNum); + common->Printf(" DeviceName : %s\n", device.DeviceName); + common->Printf(" DeviceString: %s\n", device.DeviceString); + common->Printf(" StateFlags : 0x%x\n", device.StateFlags); + common->Printf(" DeviceID : %s\n", device.DeviceID); + common->Printf(" DeviceKey : %s\n", device.DeviceKey); + common->Printf(" DeviceName : %s\n", monitor.DeviceName); + common->Printf(" DeviceString: %s\n", monitor.DeviceString); + common->Printf(" StateFlags : 0x%x\n", monitor.StateFlags); + common->Printf(" DeviceID : %s\n", monitor.DeviceID); + common->Printf(" DeviceKey : %s\n", monitor.DeviceKey); + common->Printf(" dmPosition.x : %i\n", devmode.dmPosition.x); + common->Printf(" dmPosition.y : %i\n", devmode.dmPosition.y); + common->Printf(" dmBitsPerPel : %i\n", devmode.dmBitsPerPel); + common->Printf(" dmPelsWidth : %i\n", devmode.dmPelsWidth); + common->Printf(" dmPelsHeight : %i\n", devmode.dmPelsHeight); + common->Printf(" dmDisplayFlags : 0x%x\n", devmode.dmDisplayFlags); + common->Printf(" dmDisplayFrequency: %i\n", devmode.dmDisplayFrequency); + } x = devmode.dmPosition.x; y = devmode.dmPosition.y; @@ -1405,6 +1410,28 @@ void GLimp_PreInit() // DG: not needed on this platform, so just do nothing } +// SRS - Function to get display frequency of monitor hosting the current window +static int GetDisplayFrequency( glimpParms_t parms ) +{ + HMONITOR hMonitor = MonitorFromWindow( win32.hWnd, MONITOR_DEFAULTTOPRIMARY ); + + MONITORINFOEX minfo; + minfo.cbSize = sizeof( minfo ); + if( !GetMonitorInfo( hMonitor, &minfo ) ) + { + return parms.displayHz; + } + + DEVMODE devmode; + devmode.dmSize = sizeof( devmode ); + if( !EnumDisplaySettings( minfo.szDevice, ENUM_CURRENT_SETTINGS, &devmode ) ) + { + return parms.displayHz; + } + + return devmode.dmDisplayFrequency; +} + /* =================== GLimp_Init @@ -1485,6 +1512,7 @@ bool GLimp_Init( glimpParms_t parms ) glConfig.isStereoPixelFormat = parms.stereo; glConfig.nativeScreenWidth = parms.width; glConfig.nativeScreenHeight = parms.height; + glConfig.displayFrequency = GetDisplayFrequency( parms ); glConfig.multisamples = parms.multiSamples; glConfig.pixelAspect = 1.0f; // FIXME: some monitor modes may be distorted @@ -1571,7 +1599,7 @@ bool GLimp_SetScreenParms( glimpParms_t parms ) glConfig.isStereoPixelFormat = parms.stereo; glConfig.nativeScreenWidth = parms.width; glConfig.nativeScreenHeight = parms.height; - glConfig.displayFrequency = parms.displayHz; + glConfig.displayFrequency = GetDisplayFrequency( parms ); glConfig.multisamples = parms.multiSamples; return true;