GS: Add sync to host refresh rate option

This commit is contained in:
Connor McLaughlin 2022-02-06 22:17:48 +10:00 committed by refractionpcsx2
parent a05a655037
commit ec43661664
13 changed files with 128 additions and 58 deletions

View File

@ -27,6 +27,7 @@
#include "common/StringUtil.h"
#include "pcsx2/CDVD/CDVD.h"
#include "pcsx2/Counters.h"
#include "pcsx2/Frontend/InputManager.h"
#include "pcsx2/Frontend/ImGuiManager.h"
#include "pcsx2/GS.h"
@ -361,6 +362,9 @@ void EmuThread::setFullscreen(bool fullscreen)
m_is_fullscreen = fullscreen;
GetMTGS().UpdateDisplayWindow();
GetMTGS().WaitGS();
// If we're using exclusive fullscreen, the refresh rate may have changed.
UpdateVSyncRate();
}
void EmuThread::setSurfaceless(bool surfaceless)

View File

@ -435,6 +435,7 @@ struct Pcsx2Config
PCRTCOffsets : 1,
IntegerScaling : 1,
LinearPresent : 1,
SyncToHostRefreshRate : 1,
UseDebugDevice : 1,
UseBlitSwapChain : 1,
DisableShaderCache : 1,

View File

@ -31,6 +31,8 @@
#include "ps2/HwInternal.h"
#include "Sio.h"
#include "HostDisplay.h"
#include "SPU2/spu2.h"
#ifndef PCSX2_CORE
#include "gui/App.h"
@ -46,6 +48,7 @@ using namespace Threading;
extern u8 psxhblankgate;
static const uint EECNT_FUTURE_TARGET = 0x10000000;
static int gates = 0;
static bool s_use_vsync_for_timing = false;
uint g_FrameCount = 0;
@ -346,6 +349,39 @@ double GetVerticalFrequency()
}
}
static double AdjustToHostRefreshRate(double vertical_frequency, double frame_limit)
{
if (!EmuConfig.GS.SyncToHostRefreshRate || EmuConfig.GS.LimitScalar != 1.0)
{
SPU2SetDeviceSampleRateMultiplier(1.0);
s_use_vsync_for_timing = false;
return frame_limit;
}
float host_refresh_rate;
if (!Host::GetHostDisplay()->GetHostRefreshRate(&host_refresh_rate))
{
Console.Warning("Cannot sync to host refresh since the query failed.");
SPU2SetDeviceSampleRateMultiplier(1.0);
s_use_vsync_for_timing = false;
return frame_limit;
}
const double ratio = host_refresh_rate / vertical_frequency;
const bool syncing_to_host = (ratio >= 0.95f && ratio <= 1.05f);
s_use_vsync_for_timing = (syncing_to_host && !EmuConfig.GS.SkipDuplicateFrames && EmuConfig.GS.VsyncEnable != VsyncMode::Off);
Console.WriteLn("Refresh rate: Host=%fhz Guest=%fhz Ratio=%f - %s %s", host_refresh_rate,
vertical_frequency, ratio, syncing_to_host ? "can sync" : "can't sync",
s_use_vsync_for_timing ? "and using vsync for pacing" : "and using sleep for pacing");
if (!syncing_to_host)
return frame_limit;
frame_limit *= ratio;
SPU2SetDeviceSampleRateMultiplier(ratio);
return frame_limit;
}
u32 UpdateVSyncRate()
{
// Notice: (and I probably repeat this elsewhere, but it's worth repeating)
@ -357,7 +393,7 @@ u32 UpdateVSyncRate()
const double vertical_frequency = GetVerticalFrequency();
const double frames_per_second = vertical_frequency / 2.0;
const double frame_limit = frames_per_second * EmuConfig.GS.LimitScalar;
const double frame_limit = AdjustToHostRefreshRate(vertical_frequency, frames_per_second * EmuConfig.GS.LimitScalar);
const double tick_rate = GetTickFrequency() / 2.0;
const s64 ticks = static_cast<s64>(tick_rate / std::max(frame_limit, 1.0));
@ -515,7 +551,7 @@ static __fi void frameLimitUpdateCore()
static __fi void frameLimit()
{
// Framelimiter off in settings? Framelimiter go brrr.
if (EmuConfig.GS.LimitScalar == 0.0)
if (EmuConfig.GS.LimitScalar == 0.0 || s_use_vsync_for_timing)
{
frameLimitUpdateCore();
return;

View File

@ -1371,6 +1371,7 @@ void GSApp::Init()
m_default_configuration["extrathreads_height"] = "4";
m_default_configuration["filter"] = std::to_string(static_cast<s8>(BiFiltering::PS2));
m_default_configuration["FMVSoftwareRendererSwitch"] = "0";
m_default_configuration["FullscreenMode"] = "";
m_default_configuration["fxaa"] = "0";
m_default_configuration["GSDumpCompression"] = "0";
m_default_configuration["HWDisableReadbacks"] = "0";

View File

@ -502,7 +502,6 @@ void GSRenderer::VSync(u32 field, bool registers_written)
const int fb_sprite_blits = g_perfmon.GetDisplayFramebufferSpriteBlits();
const bool fb_sprite_frame = (fb_sprite_blits > 0);
PerformanceMetrics::Update(registers_written, fb_sprite_frame);
bool skip_frame = m_frameskip;
if (GSConfig.SkipDuplicateFrames)
@ -540,6 +539,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
if (Host::BeginPresentFrame(true))
Host::EndPresentFrame();
g_gs_device->RestoreAPIState();
PerformanceMetrics::Update(registers_written, fb_sprite_frame);
return;
}
@ -572,6 +572,7 @@ void GSRenderer::VSync(u32 field, bool registers_written)
PerformanceMetrics::OnGPUPresent(Host::GetHostDisplay()->GetAndResetAccumulatedGPUTime());
}
g_gs_device->RestoreAPIState();
PerformanceMetrics::Update(registers_written, fb_sprite_frame);
// snapshot
// wx is dumb and call this from the UI thread...

View File

@ -301,6 +301,7 @@ Pcsx2Config::GSOptions::GSOptions()
PCRTCOffsets = false;
IntegerScaling = false;
LinearPresent = true;
SyncToHostRefreshRate = false;
UseDebugDevice = false;
UseBlitSwapChain = false;
DisableShaderCache = false;
@ -464,6 +465,7 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap)
#ifdef PCSX2_CORE
// These are loaded from GSWindow in wx.
SettingsWrapBitBool(SyncToHostRefreshRate);
SettingsWrapEnumEx(AspectRatio, "AspectRatio", AspectRatioNames);
SettingsWrapEnumEx(FMVAspectRatioSwitch, "FMVAspectRatioSwitch", FMVAspectRatioSwitchNames);

View File

@ -149,14 +149,6 @@ bool SndBuffer::CheckUnderrunStatus(int& nSamples, int& quietSampleCount)
return true;
}
void SndBuffer::_InitFail()
{
// If a failure occurs, just initialize the NoSound driver. This'll allow
// the game to emulate properly (hopefully), albeit without sound.
OutputModule = FindOutputModuleById(NullOut->GetIdent());
mods[OutputModule]->Init();
}
int SndBuffer::_GetApproximateDataInBuffer()
{
// WARNING: not necessarily 100% up to date by the time it's used, but it will have to do.
@ -357,13 +349,10 @@ void SndBuffer::_WriteSamples(StereoOut32* bData, int nSamples)
_WriteSamples_Safe(bData, nSamples);
}
void SndBuffer::Init()
bool SndBuffer::Init()
{
if (mods[OutputModule] == nullptr)
{
_InitFail();
return;
}
if (!mods[OutputModule])
return false;
// initialize sound buffer
// Buffer actually attempts to run ~50%, so allocate near double what
@ -372,33 +361,25 @@ void SndBuffer::Init()
m_rpos = 0;
m_wpos = 0;
try
{
const float latencyMS = SndOutLatencyMS * 16;
m_size = GetAlignedBufferSize((int)(latencyMS * SampleRate / 1000.0f));
printf("%d SampleRate: \n", SampleRate);
m_buffer = new StereoOut32[m_size];
m_underrun_freeze = false;
sndTempBuffer = new StereoOut32[SndOutPacketSize];
sndTempBuffer16 = new StereoOut16[SndOutPacketSize * 2]; // in case of leftovers.
}
catch (std::bad_alloc&)
{
// out of memory exception (most likely)
SysMessage("Out of memory error occurred while initializing SPU2.");
_InitFail();
return;
}
const float latencyMS = SndOutLatencyMS * 16;
m_size = GetAlignedBufferSize((int)(latencyMS * SampleRate / 1000.0f));
m_buffer = new StereoOut32[m_size];
m_underrun_freeze = false;
sndTempBuffer = new StereoOut32[SndOutPacketSize];
sndTempBuffer16 = new StereoOut16[SndOutPacketSize * 2]; // in case of leftovers.
sndTempProgress = 0;
soundtouchInit(); // initializes the timestretching
// initialize module
if (!mods[OutputModule]->Init())
_InitFail();
{
Cleanup();
return false;
}
return true;
}
void SndBuffer::Cleanup()

View File

@ -586,7 +586,6 @@ private:
static float eTempo;
static int ssFreeze;
static void _InitFail();
static bool CheckUnderrunStatus(int& nSamples, int& quietSampleCount);
static void soundtouchInit();
@ -614,7 +613,7 @@ private:
public:
static void UpdateTempoChangeAsyncMixing();
static void Init();
static bool Init();
static void Cleanup();
static void Write(const StereoOut32& Sample);
static void ClearContents();

View File

@ -33,8 +33,11 @@ using namespace Threading;
std::recursive_mutex mtx_SPU2Status;
static int ConsoleSampleRate = 48000;
int SampleRate = 48000;
static double DeviceSampleRateMultiplier = 1.0;
static bool IsOpened = false;
static bool IsInitialized = false;
@ -120,9 +123,44 @@ void SPU2writeDMA7Mem(u16* pMem, u32 size)
Cores[1].DoDMAwrite(pMem, size);
}
s32 SPU2reset(PS2Modes isRunningPSXMode)
static void SPU2InitSndBuffer()
{
int requiredSampleRate = (isRunningPSXMode == PS2Modes::PSX) ? 44100 : 48000;
Console.WriteLn("Initializing SndBuffer at sample rate of %u...", SampleRate);
if (SndBuffer::Init())
return;
if (SampleRate != ConsoleSampleRate)
{
// TODO: Resample on our side...
const int original_sample_rate = SampleRate;
Console.Error("Failed to init SPU2 at adjusted sample rate %u, trying console rate.", SampleRate);
SampleRate = ConsoleSampleRate;
if (SndBuffer::Init())
return;
SampleRate = original_sample_rate;
}
// just use nullout
OutputModule = FindOutputModuleById(NullOut->GetIdent());
if (!SndBuffer::Init())
pxFailRel("Failed to initialize nullout.");
}
static void SPU2UpdateSampleRate()
{
const int new_sample_rate = static_cast<int>(std::round(static_cast<double>(ConsoleSampleRate) * DeviceSampleRateMultiplier));
if (SampleRate == new_sample_rate)
return;
SndBuffer::Cleanup();
SampleRate = new_sample_rate;
SPU2InitSndBuffer();
}
static void SPU2InternalReset(PS2Modes isRunningPSXMode)
{
ConsoleSampleRate = (isRunningPSXMode == PS2Modes::PSX) ? 44100 : 48000;
if (isRunningPSXMode == PS2Modes::PS2)
{
@ -136,25 +174,24 @@ s32 SPU2reset(PS2Modes isRunningPSXMode)
Cores[0].Init(0);
Cores[1].Init(1);
}
}
if (SampleRate != requiredSampleRate)
{
SampleRate = requiredSampleRate;
SndBuffer::Cleanup();
try
{
SndBuffer::Init();
}
catch (std::exception& ex)
{
fprintf(stderr, "SPU2 Error: Could not initialize device, or something.\nReason: %s", ex.what());
SPU2close();
return -1;
}
}
s32 SPU2reset(PS2Modes isRunningPSXMode)
{
SPU2InternalReset(isRunningPSXMode);
SPU2UpdateSampleRate();
return 0;
}
void SPU2SetDeviceSampleRateMultiplier(double multiplier)
{
if (DeviceSampleRateMultiplier == multiplier)
return;
DeviceSampleRateMultiplier = multiplier;
SPU2UpdateSampleRate();
}
s32 SPU2init()
{
assert(regtable[0x400] == nullptr);
@ -207,7 +244,7 @@ s32 SPU2init()
}
}
SPU2reset(PS2Modes::PS2);
SPU2InternalReset(PS2Modes::PS2);
DMALogOpen();
InitADSR();
@ -289,7 +326,8 @@ s32 SPU2open()
try
{
SndBuffer::Init();
SampleRate = static_cast<int>(std::round(static_cast<double>(ConsoleSampleRate) * DeviceSampleRateMultiplier));
SPU2InitSndBuffer();
#if defined(_WIN32) && !defined(PCSX2_CORE)
DspLoadLibrary(dspPlugin, dspPluginModule);

View File

@ -33,6 +33,7 @@ s32 SPU2open();
void SPU2close();
void SPU2shutdown();
void SPU2SetOutputPaused(bool paused);
void SPU2SetDeviceSampleRateMultiplier(double multiplier);
void SPU2write(u32 mem, u16 value);
u16 SPU2read(u32 mem);

View File

@ -886,6 +886,7 @@ void AppConfig::GSWindowOptions::LoadSave(IniInterface& ini)
// WARNING: array must be NULL terminated to compute it size
NULL};
g_Conf->EmuOptions.GS.SyncToHostRefreshRate = ini.EntryBitBool(L"SyncToHostRefreshRate", g_Conf->EmuOptions.GS.SyncToHostRefreshRate, g_Conf->EmuOptions.GS.SyncToHostRefreshRate);
ini.EnumEntry(L"AspectRatio", g_Conf->EmuOptions.GS.AspectRatio, AspectRatioNames, g_Conf->EmuOptions.GS.AspectRatio);
if (ini.IsLoading())
EmuConfig.CurrentAspectRatio = g_Conf->EmuOptions.GS.AspectRatio;

View File

@ -285,6 +285,7 @@ namespace Panels
pxCheckBox* m_check_HideMouse;
pxCheckBox* m_check_DclickFullscreen;
pxCheckBox* m_check_SyncToHostRefreshRate;
wxTextCtrl* m_text_WindowWidth;
wxTextCtrl* m_text_WindowHeight;

View File

@ -74,6 +74,7 @@ Panels::GSWindowSettingsPanel::GSWindowSettingsPanel(wxWindow* parent)
// Implement custom hotkeys (Alt + Enter) with translatable string intact + not blank in GUI.
m_check_Fullscreen = new pxCheckBox(this, _("Start in fullscreen mode by default") + wxString(" (") + wxGetApp().GlobalAccels->findKeycodeWithCommandId("FullscreenToggle").toTitleizedString() + wxString(")"));
m_check_DclickFullscreen = new pxCheckBox(this, _("Double-click toggles fullscreen mode"));
m_check_SyncToHostRefreshRate = new pxCheckBox(this, _("Sync To Host Refresh Rate"));
m_combo_FMVAspectRatioSwitch->SetToolTip(pxEt(L"Off: Disables temporary aspect ratio switch. (It will use the above setting from Aspect Ratio instead of FMV Aspect Ratio Override.)\n\n"
L"Auto 4:3/3:2: Temporarily switch to a 4:3 aspect ratio while an FMV plays to correctly display a 4:3 FMV. Will use 3:2 is the resolution is 480P\n\n"
@ -136,6 +137,7 @@ Panels::GSWindowSettingsPanel::GSWindowSettingsPanel(wxWindow* parent)
*this += m_check_Fullscreen;
*this += m_check_DclickFullscreen;
*this += m_check_SyncToHostRefreshRate;
*this += new wxStaticLine(this) | StdExpand();
*this += s_vsync | StdExpand();
@ -169,6 +171,7 @@ void Panels::GSWindowSettingsPanel::ApplyConfigToGui(AppConfig& configToApply, i
m_text_Zoom->ChangeValue(wxString::FromDouble(gsconf.Zoom, 2));
m_check_DclickFullscreen->SetValue(conf.IsToggleFullscreenOnDoubleClick);
m_check_SyncToHostRefreshRate->SetValue(gsconf.SyncToHostRefreshRate);
m_text_WindowWidth->ChangeValue(wxsFormat(L"%d", conf.WindowSize.GetWidth()));
m_text_WindowHeight->ChangeValue(wxsFormat(L"%d", conf.WindowSize.GetHeight()));
@ -199,6 +202,7 @@ void Panels::GSWindowSettingsPanel::Apply()
gsconf.VsyncEnable = static_cast<VsyncMode>(m_combo_vsync->GetSelection());
appconf.IsToggleFullscreenOnDoubleClick = m_check_DclickFullscreen->GetValue();
gsconf.SyncToHostRefreshRate = m_check_SyncToHostRefreshRate->GetValue();
long xr, yr = 1;