GS: Manually throttle fullscreen UI rendering

Fixes rendering at thousands of FPS when pausing if FSUI is active.

Avoids flickering when recreating swap chain in vulkan on menu open,
because we're no longer doing it.
This commit is contained in:
Connor McLaughlin 2022-12-04 15:09:22 +10:00 committed by refractionpcsx2
parent aea6a9f534
commit 77e9938f0f
27 changed files with 89 additions and 90 deletions

View File

@ -265,7 +265,8 @@ bool Host::AcquireHostDisplay(RenderAPI api, bool clear_state_on_fail)
if (!g_host_display)
return false;
if (!g_host_display->CreateDevice(wi.value()) || !g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize())
if (!g_host_display->CreateDevice(wi.value(), Host::GetEffectiveVSyncMode()) ||
!g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize())
{
ReleaseHostDisplay(clear_state_on_fail);
return false;
@ -283,12 +284,6 @@ void Host::ReleaseHostDisplay(bool clear_state)
g_host_display.reset();
}
VsyncMode Host::GetEffectiveVSyncMode()
{
// Never vsync! We want to finish as quickly as possible.
return VsyncMode::Off;
}
bool Host::BeginPresentFrame(bool frame_skip)
{
if (g_host_display->BeginPresent(frame_skip))

View File

@ -2012,7 +2012,7 @@ DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
g_emu_thread->connectDisplaySignals(m_display_widget);
if (!g_host_display->CreateDevice(wi.value()))
if (!g_host_display->CreateDevice(wi.value(), Host::GetEffectiveVSyncMode()))
{
QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context."));
destroyDisplayWidget(true);

View File

@ -931,26 +931,6 @@ void Host::ReleaseHostDisplay(bool clear_state)
g_emu_thread->releaseHostDisplay(clear_state);
}
VsyncMode Host::GetEffectiveVSyncMode()
{
// Force vsync on when running big picture UI, and paused or no VM.
// We check the "running FSUI" flag here, because that way we set the initial vsync
// state when initalizing to on, avoiding an unnecessary switch.
if (FullscreenUI::HasActiveWindow() || (!FullscreenUI::IsInitialized() && g_emu_thread->isRunningFullscreenUI()))
{
const VMState state = VMManager::GetState();
if (state == VMState::Shutdown || state == VMState::Paused)
return VsyncMode::On;
}
// Force vsync off when not running at 100% speed.
if (EmuConfig.GS.LimitScalar != 1.0f)
return VsyncMode::Off;
// Otherwise use the config setting.
return EmuConfig.GS.VsyncEnable;
}
bool Host::BeginPresentFrame(bool frame_skip)
{
if (!g_host_display->BeginPresent(frame_skip))

View File

@ -346,7 +346,6 @@ void CommonHost::OnVMDestroyed()
void CommonHost::OnVMPaused()
{
InputManager::PauseVibration();
FullscreenUI::OnVMPaused();
#ifdef ENABLE_ACHIEVEMENTS
Achievements::OnPaused(true);
@ -357,8 +356,6 @@ void CommonHost::OnVMPaused()
void CommonHost::OnVMResumed()
{
FullscreenUI::OnVMResumed();
#ifdef ENABLE_ACHIEVEMENTS
Achievements::OnPaused(false);
#endif

View File

@ -46,7 +46,6 @@ static void HotkeyAdjustTargetSpeed(double delta)
EmuConfig.Framerate.NominalScalar = std::max(min_speed, EmuConfig.GS.LimitScalar + delta);
VMManager::SetLimiterMode(LimiterModeType::Nominal);
gsUpdateFrequency(EmuConfig);
GetMTGS().UpdateVSyncMode();
Host::AddIconOSDMessage("SpeedChanged", ICON_FA_CLOCK,
fmt::format("Target speed set to {:.0f}%.", std::round(EmuConfig.Framerate.NominalScalar * 100.0)), Host::OSD_QUICK_DURATION);
}

View File

@ -210,7 +210,7 @@ void D3D11HostDisplay::SetVSync(VsyncMode mode)
m_vsync_mode = mode;
}
bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi)
bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync)
{
UINT create_flags = 0;
if (EmuConfig.GS.UseDebugDevice)
@ -314,7 +314,7 @@ bool D3D11HostDisplay::CreateDevice(const WindowInfo& wi)
}
m_window_info = wi;
m_vsync_mode = Host::GetEffectiveVSyncMode();
m_vsync_mode = vsync;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr))
return false;

View File

@ -43,7 +43,7 @@ public:
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override;
bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override;
bool SetupDevice() override;
bool MakeCurrent() override;

View File

@ -136,7 +136,7 @@ void D3D12HostDisplay::SetVSync(VsyncMode mode)
m_vsync_mode = mode;
}
bool D3D12HostDisplay::CreateDevice(const WindowInfo& wi)
bool D3D12HostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync)
{
ComPtr<IDXGIFactory> temp_dxgi_factory;
HRESULT hr = CreateDXGIFactory(IID_PPV_ARGS(temp_dxgi_factory.put()));
@ -190,7 +190,7 @@ bool D3D12HostDisplay::CreateDevice(const WindowInfo& wi)
}
m_window_info = wi;
m_vsync_mode = Host::GetEffectiveVSyncMode();
m_vsync_mode = vsync;
if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain(nullptr))
return false;

View File

@ -49,7 +49,7 @@ public:
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override;
bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override;
bool SetupDevice() override;
bool MakeCurrent() override;

View File

@ -623,24 +623,6 @@ void FullscreenUI::OnVMStarted()
});
}
void FullscreenUI::OnVMPaused()
{
if (!IsInitialized())
return;
// Force vsync on.
GetMTGS().UpdateVSyncMode();
}
void FullscreenUI::OnVMResumed()
{
if (!IsInitialized())
return;
// Restore game vsync.
GetMTGS().UpdateVSyncMode();
}
void FullscreenUI::OnVMDestroyed()
{
if (!IsInitialized())
@ -652,7 +634,6 @@ void FullscreenUI::OnVMDestroyed()
s_pause_menu_was_open = false;
SwitchToLanding();
GetMTGS().UpdateVSyncMode();
});
}

View File

@ -30,8 +30,6 @@ namespace FullscreenUI
bool HasActiveWindow();
void CheckForConfigChanges(const Pcsx2Config& old_config);
void OnVMStarted();
void OnVMPaused();
void OnVMResumed();
void OnVMDestroyed();
void OnRunningGameChanged(std::string path, std::string serial, std::string title, u32 crc);
void OpenPauseMenu();

View File

@ -64,7 +64,7 @@ public:
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override;
bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override;
bool SetupDevice() override;
bool MakeCurrent() override;
bool DoneCurrent() override;

View File

@ -103,7 +103,7 @@ void MetalHostDisplay::DetachSurfaceOnMainThread()
m_layer = nullptr;
}
bool MetalHostDisplay::CreateDevice(const WindowInfo& wi)
bool MetalHostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync)
{ @autoreleasepool {
m_window_info = wi;
pxAssertRel(!m_dev.dev, "Device already created!");
@ -152,7 +152,7 @@ bool MetalHostDisplay::CreateDevice(const WindowInfo& wi)
{
AttachSurfaceOnMainThread();
});
SetVSync(Host::GetEffectiveVSyncMode());
SetVSync(vsync);
return true;
}
else

View File

@ -143,7 +143,7 @@ void OpenGLHostDisplay::UpdateTexture(HostDisplayTexture* texture, u32 x, u32 y,
void OpenGLHostDisplay::SetVSync(VsyncMode mode)
{
if (m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless)
if (m_vsync_mode == mode || m_gl_context->GetWindowInfo().type == WindowInfo::Type::Surfaceless)
return;
// Window framebuffer has to be bound to call SetSwapInterval.
@ -155,6 +155,7 @@ void OpenGLHostDisplay::SetVSync(VsyncMode mode)
m_gl_context->SetSwapInterval(static_cast<s32>(mode != VsyncMode::Off));
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
m_vsync_mode = mode;
}
const char* OpenGLHostDisplay::GetGLSLVersionString() const
@ -198,7 +199,7 @@ bool OpenGLHostDisplay::HasSurface() const
return m_window_info.type != WindowInfo::Type::Surfaceless;
}
bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi)
bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync)
{
m_gl_context = GL::Context::Create(wi);
if (!m_gl_context)
@ -209,7 +210,7 @@ bool OpenGLHostDisplay::CreateDevice(const WindowInfo& wi)
}
m_window_info = m_gl_context->GetWindowInfo();
m_vsync_mode = Host::GetEffectiveVSyncMode();
m_vsync_mode = vsync;
return true;
}

View File

@ -38,7 +38,7 @@ public:
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override;
bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override;
bool SetupDevice() override;
bool MakeCurrent() override;

View File

@ -274,10 +274,9 @@ void VulkanHostDisplay::SetVSync(VsyncMode mode)
m_vsync_mode = mode;
}
bool VulkanHostDisplay::CreateDevice(const WindowInfo& wi)
bool VulkanHostDisplay::CreateDevice(const WindowInfo& wi, VsyncMode vsync)
{
WindowInfo local_wi(wi);
const VsyncMode vsync = Host::GetEffectiveVSyncMode();
const bool debug_device = EmuConfig.GS.UseDebugDevice;
if (!Vulkan::Context::Create(EmuConfig.GS.Adapter, &local_wi, &m_swap_chain, GetPreferredPresentModeForVsyncMode(vsync),
EmuConfig.GS.ThreadedPresentation, debug_device, debug_device))

View File

@ -27,7 +27,7 @@ public:
bool HasDevice() const override;
bool HasSurface() const override;
bool CreateDevice(const WindowInfo& wi) override;
bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) override;
bool SetupDevice() override;
bool MakeCurrent() override;

View File

@ -74,6 +74,10 @@ void gsUpdateFrequency(Pcsx2Config& config)
config.GS.LimitScalar = 0.0f;
}
#ifdef PCSX2_CORE
GetMTGS().UpdateVSyncMode();
#endif
UpdateVSyncRate();
}

View File

@ -77,6 +77,7 @@ static HRESULT s_hr = E_FAIL;
Pcsx2Config::GSOptions GSConfig;
static RenderAPI s_render_api;
static u64 s_next_manual_present_time;
int GSinit()
{
@ -554,6 +555,33 @@ void GSPresentCurrentFrame()
g_gs_renderer->PresentCurrentFrame();
}
void GSThrottlePresentation()
{
if (g_host_display->GetVsyncMode() != VsyncMode::Off)
{
// Let vsync take care of throttling.
return;
}
// Manually throttle presentation when vsync isn't enabled, so we don't try to render the
// fullscreen UI at thousands of FPS and make the gpu go brrrrrrrr.
const float surface_refresh_rate = g_host_display->GetWindowInfo().surface_refresh_rate;
const float throttle_rate = (surface_refresh_rate > 0.0f) ? surface_refresh_rate : 60.0f;
const u64 sleep_period = static_cast<u64>(static_cast<double>(GetTickFrequency()) / static_cast<double>(throttle_rate));
const u64 current_ts = GetCPUTicks();
// Allow it to fall behind/run ahead up to 2*period. Sleep isn't that precise, plus we need to
// allow time for the actual rendering.
const u64 max_variance = sleep_period * 2;
if (static_cast<u64>(std::abs(static_cast<s64>(current_ts - s_next_manual_present_time))) > max_variance)
s_next_manual_present_time = current_ts + sleep_period;
else
s_next_manual_present_time += sleep_period;
Threading::SleepUntil(s_next_manual_present_time);
}
#ifndef PCSX2_CORE
void GSkeyEvent(const HostKeyEvent& e)

View File

@ -72,6 +72,7 @@ int GSfreeze(FreezeAction mode, freezeData* data);
void GSQueueSnapshot(const std::string& path, u32 gsdump_frames = 0);
void GSStopGSDump();
void GSPresentCurrentFrame();
void GSThrottlePresentation();
#ifndef PCSX2_CORE
void GSkeyEvent(const HostKeyEvent& e);
void GSconfigure();

View File

@ -24,6 +24,7 @@
#include <optional>
#include <vector>
#ifndef PCSX2_CORE
struct HostKeyEvent
{
enum class Type
@ -43,6 +44,7 @@ struct HostKeyEvent
Type type;
u32 key;
};
#endif
namespace Host
{

View File

@ -16,9 +16,15 @@
#include "PrecompiledHeader.h"
#include "HostDisplay.h"
#ifdef PCSX2_CORE
#include "VMManager.h"
#endif
#include "common/Assertions.h"
#include "common/Console.h"
#include "common/StringUtil.h"
#include <cerrno>
#include <cmath>
#include <cstring>
@ -128,6 +134,22 @@ std::string HostDisplay::GetFullscreenModeString(u32 width, u32 height, float re
return StringUtil::StdStringFromFormat("%u x %u @ %f hz", width, height, refresh_rate);
}
VsyncMode Host::GetEffectiveVSyncMode()
{
#ifdef PCSX2_CORE
const bool has_vm = VMManager::GetState() != VMState::Shutdown;
#else
const bool has_vm = false;
#endif
// Force vsync off when not running at 100% speed.
if (has_vm && EmuConfig.GS.LimitScalar != 1.0f)
return VsyncMode::Off;
// Otherwise use the config setting.
return EmuConfig.GS.VsyncEnable;
}
#ifdef ENABLE_OPENGL
#include "Frontend/OpenGLHostDisplay.h"
#endif
@ -173,4 +195,3 @@ std::unique_ptr<HostDisplay> HostDisplay::CreateForAPI(RenderAPI api)
return {};
}
}

View File

@ -84,6 +84,7 @@ public:
__fi s32 GetWindowWidth() const { return static_cast<s32>(m_window_info.surface_width); }
__fi s32 GetWindowHeight() const { return static_cast<s32>(m_window_info.surface_height); }
__fi float GetWindowScale() const { return m_window_info.surface_scale; }
__fi VsyncMode GetVsyncMode() const { return m_vsync_mode; }
/// Changes the alignment for this display (screen positioning).
__fi Alignment GetDisplayAlignment() const { return m_display_alignment; }
@ -98,7 +99,7 @@ public:
virtual bool HasSurface() const = 0;
/// Creates the rendering/GPU device. This should be called on the thread which owns the window.
virtual bool CreateDevice(const WindowInfo& wi) = 0;
virtual bool CreateDevice(const WindowInfo& wi, VsyncMode vsync) = 0;
/// Fully initializes the rendering device. This should be called on the GS thread.
virtual bool SetupDevice() = 0;

View File

@ -298,16 +298,14 @@ void SysMtgsThread::MainLoop()
while (true)
{
// Performance note: Both of these perform cancellation tests, but pthread_testcancel
// is very optimized (only 1 instruction test in most cases), so no point in trying
// to avoid it.
#ifdef PCSX2_CORE
if (m_run_idle_flag.load(std::memory_order_acquire) && VMManager::GetState() != VMState::Running)
{
if (!m_sem_event.CheckForWork())
{
GSPresentCurrentFrame();
GSThrottlePresentation();
}
}
else
{

View File

@ -27,11 +27,16 @@ void PADclose();
u8 PADstartPoll(int port, int slot);
u8 PADpoll(u8 value);
bool PADcomplete();
HostKeyEvent* PADkeyEvent();
void PADconfigure();
s32 PADfreeze(FreezeAction mode, freezeData* data);
s32 PADsetSlot(u8 port, u8 slot);
#ifndef PCSX2_CORE
HostKeyEvent* PADkeyEvent();
#if defined(__unix__) || defined(__APPLE__)
void PADWriteEvent(HostKeyEvent& evt);
#endif
#endif

View File

@ -1393,7 +1393,6 @@ void VMManager::SetLimiterMode(LimiterModeType type)
EmuConfig.LimiterMode = type;
gsUpdateFrequency(EmuConfig);
GetMTGS().UpdateVSyncMode();
}
void VMManager::FrameAdvance(u32 num_frames /*= 1*/)
@ -1635,7 +1634,6 @@ void VMManager::CheckForFramerateConfigChanges(const Pcsx2Config& old_config)
gsUpdateFrequency(EmuConfig);
UpdateVSyncRate();
frameLimitReset();
GetMTGS().UpdateVSyncMode();
}
void VMManager::CheckForPatchConfigChanges(const Pcsx2Config& old_config)

View File

@ -129,7 +129,8 @@ bool Host::AcquireHostDisplay(RenderAPI api, bool clear_state_on_fail)
if (!g_host_display)
return false;
if (!g_host_display->CreateDevice(g_gs_window_info) || !g_host_display->SetupDevice() || !ImGuiManager::Initialize())
if (!g_host_display->CreateDevice(g_gs_window_info, Host::GetEffectiveVSyncMode()) ||
!g_host_display->SetupDevice() || !ImGuiManager::Initialize())
{
g_host_display.reset();
return false;
@ -151,16 +152,6 @@ void Host::ReleaseHostDisplay(bool clear_state_on_fail)
sApp.CloseGsPanel();
}
VsyncMode Host::GetEffectiveVSyncMode()
{
// Force vsync off when not running at 100% speed.
if (EmuConfig.GS.LimitScalar != 1.0f)
return VsyncMode::Off;
// Otherwise use the config setting.
return EmuConfig.GS.VsyncEnable;
}
bool Host::BeginPresentFrame(bool frame_skip)
{
CheckForGSWindowResize();