improve framepacing with video sync

add vrr support
This commit is contained in:
barbudreadmon committed 2026-10-28 23:33:57 +01:00
1 parent 56db8a4fb4
commit d2bb7d3475
9 files changed
+122 -2

No files matched your search

+1
View File
@@ -1316,6 +1316,7 @@ BEGIN
MENUITEM "Enable Windowed Fullscreen (DX9/DX11 Blitters)", MENU_WINFS
MENUITEM "Enable VSync (Windowed)", MENU_VIDEOVSYNC
MENUITEM "Enable DWM Sync (Windowed)", MENU_DWMFIX
MENUITEM "Enable Variable Refresh Rate (DX11, Win10+)", MENU_VRR
MENUITEM "Auto Frameskip", MENU_AUTOFRAMESKIP
MENUITEM "Enable RunAhead", MENU_RUNAHEAD
END
+3
View File
@@ -296,6 +296,7 @@ int ConfigAppLoad()
VAR(bVidDX9WinFullscreen);
VAR(bVidVSync);
VAR(bVidDWMSync);
VAR(bVidVRR);
VAR(bVidScanlines);
VAR(nVidScanIntensity);
@@ -687,6 +688,8 @@ int ConfigAppSave()
VAR(bVidVSync);
_ftprintf(h, _T("\n// If non-zero, try to synchronise to DWM on Windows 7+, this fixes frame stuttering problems.\n"));
VAR(bVidDWMSync);
_ftprintf(h, _T("\n// If non-zero, use variable refresh rate (G-Sync/FreeSync) with the DX11 blitter on Windows 10+\n"));
VAR(bVidVRR);
_ftprintf(h, _T("\n// Transfer method: 0 = blit from system memory / use driver/DirectX texture management;\n"));
_ftprintf(h, _T("// 1 = copy to a video memory surface, then use bltfast();\n"));
_ftprintf(h, _T("// -1 = autodetect for DirectDraw, equals 1 for Direct3D\n"));
+1
View File
@@ -1072,6 +1072,7 @@ void MenuUpdate()
CheckMenuItem(hMenu, MENU_WINFS, bVidDX9WinFullscreen ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem(hMenu, MENU_DWMFIX, bVidDWMSync ? MF_CHECKED : MF_UNCHECKED);
CheckMenuItem(hMenu, MENU_VRR, bVidVRR ? MF_CHECKED : MF_UNCHECKED);
var = nVidSelect + MENU_BLITTER_1;
CheckMenuRadioItem(hMenu, MENU_BLITTER_1, MENU_BLITTER_8, var, MF_BYCOMMAND);
+1
View File
@@ -602,6 +602,7 @@
#define MENU_WINFS 10101
#define MENU_FULL 10102
#define MENU_DWMFIX 10103
#define MENU_VRR 10104
#define MENU_NOSTRETCH 10110
#define MENU_STRETCH 10111
#define MENU_ASPECT 10112
+58
View File
@@ -257,6 +257,62 @@ void do_shonky_profile()
}
#endif
// Present pacing when no video sync is active.
// Frames are produced when the audio engine signals a free segment, which happens on
// its own period (~10ms on Vista+), so frame times alternate e.g. 20/20/10ms.
// Presents are held back to a steady QPC schedule which follows the audio clock.
static LARGE_INTEGER qpcPaceFreq;
static LONGLONG nPaceNext = 0;
static bool RunPaceActive()
{
if (bVidVSync || (bVidDWMSync && !nVidFullscreen)) return false;
if (bAppDoFast || kNetGame) return false;
return true;
}
static void RunPacePresent()
{
if (!RunPaceActive()) {
nPaceNext = 0;
return;
}
if (qpcPaceFreq.QuadPart == 0) {
QueryPerformanceFrequency(&qpcPaceFreq);
}
LONGLONG nPeriod;
if (bAudPlaying && nBurnSoundRate > 0 && nBurnSoundLen > 0) {
nPeriod = qpcPaceFreq.QuadPart * nBurnSoundLen / nBurnSoundRate;
} else {
nPeriod = qpcPaceFreq.QuadPart * 100 / nAppVirtualFps;
}
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
LONGLONG nLead = nPaceNext - now.QuadPart;
if (nPaceNext == 0 || nLead < 0) {
// late (or first frame): present now, the schedule follows
nPaceNext = now.QuadPart;
} else if (nLead > nPeriod) {
// schedule drifted ahead of the frame source, pull it back
nPaceNext = now.QuadPart + nPeriod;
}
const LONGLONG nSleepMargin = qpcPaceFreq.QuadPart * 2 / 1000;
for (;;) {
QueryPerformanceCounter(&now);
LONGLONG nRemain = nPaceNext - now.QuadPart;
if (nRemain <= 0) break;
Sleep((bEnableHighResTimer && nRemain > nSleepMargin) ? 1 : 0);
}
nPaceNext += nPeriod;
}
// With or without sound, run one frame.
// If bDraw is true, it's the last frame before we are up to date, and so we should draw the screen
int RunFrame(int bDraw, int bPause)
@@ -267,6 +323,7 @@ int RunFrame(int bDraw, int bPause)
if (bShowFPS) DisplayFPSCheckState();
if (bPrevDraw && !bPause) {
RunPacePresent();
VidPaint(0); // paint the screen (no need to validate)
if (!nVidFullscreen && bVidDWMSync) // Sync to DWM (Win7+, windowed)
@@ -523,6 +580,7 @@ int RunReset()
{
// Reset the speed throttling code
nNormalLast = 0; nNormalFrac = 0;
nPaceNext = 0;
// Reset FPS display
DisplayFPSInit();
+14
View File
@@ -2295,6 +2295,8 @@ static void OnCommand(HWND /*hDlg*/, int id, HWND /*hwndCtl*/, UINT codeNotify)
bVidDWMSync = !bVidDWMSync;
if (bVidDWMSync && bVidVSync)
bVidVSync = 0;
if (bVidDWMSync)
bVidVRR = 0;
POST_INITIALISE_MESSAGE;
break;
@@ -2909,6 +2911,18 @@ static void OnCommand(HWND /*hDlg*/, int id, HWND /*hwndCtl*/, UINT codeNotify)
bVidVSync = !bVidVSync;
if (bVidVSync && bVidDWMSync)
bVidDWMSync = 0;
if (bVidVSync)
bVidVRR = 0;
POST_INITIALISE_MESSAGE;
break;
case MENU_VRR:
bVidVRR = !bVidVRR;
if (bVidVRR) {
bVidVSync = 0;
bVidDWMSync = 0;
}
POST_INITIALISE_MESSAGE;
break;
+1
View File
@@ -241,6 +241,7 @@ extern INT64 nVidBlitterOpt[];
extern INT32 bVidFullStretch;
extern INT32 bVidTripleBuffer;
extern INT32 bVidVSync;
extern INT32 bVidVRR;
extern double dVidCubicB;
extern double dVidCubicC;
extern INT32 bVidDX9Bilinear;
+1
View File
@@ -86,6 +86,7 @@ INT32 bVidFullStretch = 0; // 1 = stretch to fill the entire window/screen
INT32 bVidCorrectAspect = 1; // 1 = stretch to fill the window/screen while maintaining the correct aspect ratio
INT32 bVidIntegerScale = 0; // Wants integer scaling
INT32 bVidVSync = 0; // 1 = sync blits/pageflips/presents to the screen
INT32 bVidVRR = 0; // 1 = variable refresh rate (G-Sync/FreeSync), DX11 blitter only
INT32 bVidTripleBuffer = 0; // 1 = use triple buffering
INT32 bVidBilinear = 1; // 1 = enable bi-linear filtering (D3D blitter)
INT32 bVidScanlines = 0; // 1 = draw scanlines
+42 -2
View File
@@ -53,6 +53,7 @@
static const GUID FBNEO_IID_IDXGIDevice = { 0x54ec77fa, 0x1377, 0x44e6, { 0x8c, 0x32, 0x88, 0xfd, 0x5f, 0x44, 0xc8, 0x4c } };
static const GUID FBNEO_IID_IDXGIDevice1 = { 0x77db970f, 0x6276, 0x48ba, { 0xba, 0x28, 0x07, 0x01, 0x43, 0xb4, 0x39, 0x2c } };
static const GUID FBNEO_IID_IDXGIFactory = { 0x7b7166ec, 0x21c7, 0x44ae, { 0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69 } };
static const GUID FBNEO_IID_IDXGIFactory5 = { 0x7632e1f5, 0xee65, 0x4dca, { 0x87, 0xfd, 0x84, 0xcd, 0x75, 0xf8, 0x83, 0x8d } };
static const GUID FBNEO_IID_ID3D11Texture2D = { 0x6f15aaf2, 0xd208, 0x4e89, { 0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, 0x4f, 0x9c } };
// ID3D11ShaderReflection changed IID between d3dcompiler versions
static const GUID FBNEO_IID_ID3D11ShaderReflection_47 = { 0x8d536ca1, 0x0cca, 0x4956, { 0xa8, 0x37, 0x78, 0x69, 0x63, 0x75, 0x55, 0x84 } };
@@ -61,6 +62,9 @@ static const GUID FBNEO_IID_ID3D11ShaderReflection_43 = { 0x0a233719, 0x3960, 0x
// DXGI values that older headers may not have
#define FBNEO_DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL ((DXGI_SWAP_EFFECT)3)
#define FBNEO_DXGI_SWAP_EFFECT_FLIP_DISCARD ((DXGI_SWAP_EFFECT)4)
#define FBNEO_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING 0x800
#define FBNEO_DXGI_PRESENT_ALLOW_TEARING 0x200
#define FBNEO_DXGI_FEATURE_PRESENT_ALLOW_TEARING 0
// ----------------------------------------------------------------------------
// Runtime-loaded DLLs
@@ -165,6 +169,7 @@ static ID3D11RenderTargetView* pRenderTarget = NULL;
static D3D_FEATURE_LEVEL nFeatureLevel = D3D_FEATURE_LEVEL_9_1;
static bool bWarpDevice = false;
static DXGI_SWAP_EFFECT nSwapEffect = DXGI_SWAP_EFFECT_DISCARD;
static UINT nSwapChainFlags = 0;
static UINT nSwapChainWidth = 0, nSwapChainHeight = 0;
static UINT nDeviceGeneration = 0; // bumped every time a device is created (OSD textures follow it)
static bool bDeviceLost = false;
@@ -2579,7 +2584,7 @@ static int d3d11ResizeSwapChain(UINT nWidth, UINT nHeight)
pContext->OMSetRenderTargets(0, NULL, NULL);
D3D11_RELEASE(pRenderTarget);
if (FAILED(pSwapChain->ResizeBuffers(0, nWidth, nHeight, DXGI_FORMAT_UNKNOWN, 0))) {
if (FAILED(pSwapChain->ResizeBuffers(0, nWidth, nHeight, DXGI_FORMAT_UNKNOWN, nSwapChainFlags))) {
return 1;
}
@@ -2589,6 +2594,27 @@ static int d3d11ResizeSwapChain(UINT nWidth, UINT nHeight)
return d3d11CreateRenderTarget();
}
// IDXGIFactory5::CheckFeatureSupport() is called through the vtable, so no DXGI 1.5 headers are needed
static bool d3d11TearingSupported(IDXGIFactory* pFactory)
{
typedef HRESULT (STDMETHODCALLTYPE* pfnCheckFeatureSupport)(void*, UINT, void*, UINT);
void* pFactory5 = NULL;
BOOL bAllowTearing = FALSE;
if (FAILED(pFactory->QueryInterface(FBNEO_IID_IDXGIFactory5, &pFactory5))) {
return false;
}
pfnCheckFeatureSupport pCheckFeatureSupport = (pfnCheckFeatureSupport)((*(void***)pFactory5)[28]);
if (FAILED(pCheckFeatureSupport(pFactory5, FBNEO_DXGI_FEATURE_PRESENT_ALLOW_TEARING, &bAllowTearing, sizeof(bAllowTearing)))) {
bAllowTearing = FALSE;
}
((IUnknown*)pFactory5)->Release();
return bAllowTearing != FALSE;
}
static int d3d11CreateSwapChain()
{
IDXGIDevice* pDXGIDevice = NULL;
@@ -2613,6 +2639,9 @@ static int d3d11CreateSwapChain()
nSwapChainWidth = (rect.right > 0) ? rect.right : 1;
nSwapChainHeight = (rect.bottom > 0) ? rect.bottom : 1;
bool bTearing = bVidVRR && d3d11TearingSupported(pFactory);
nSwapChainFlags = 0;
// Prefer the flip model (Win10: FLIP_DISCARD, Win8: FLIP_SEQUENTIAL), fall back to the blt model (Win7)
static const DXGI_SWAP_EFFECT SwapEffects[] = { FBNEO_DXGI_SWAP_EFFECT_FLIP_DISCARD, FBNEO_DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, DXGI_SWAP_EFFECT_DISCARD };
@@ -2630,9 +2659,11 @@ static int d3d11CreateSwapChain()
scd.OutputWindow = hD3D11View;
scd.Windowed = TRUE;
scd.SwapEffect = SwapEffects[i];
scd.Flags = (bFlip && bTearing) ? FBNEO_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0;
if (SUCCEEDED(pFactory->CreateSwapChain(pDevice, &scd, &pSwapChain))) {
nSwapEffect = SwapEffects[i];
nSwapChainFlags = scd.Flags;
} else {
pSwapChain = NULL;
}
@@ -3447,7 +3478,10 @@ static int d3d11Paint(int bValidate)
return 1;
}
HRESULT hr = pSwapChain->Present((bVidVSync && !EmulatorAppIsFast()) ? 1 : 0, 0);
UINT nSyncInterval = (bVidVSync && !EmulatorAppIsFast()) ? 1 : 0;
// VRR: present immediately, the display follows our own frame pacing
UINT nPresentFlags = (nSyncInterval == 0 && (nSwapChainFlags & FBNEO_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)) ? FBNEO_DXGI_PRESENT_ALLOW_TEARING : 0;
HRESULT hr = pSwapChain->Present(nSyncInterval, nPresentFlags);
bInPaint = false;
@@ -3489,6 +3523,12 @@ static int d3d11GetSettings(InterfaceInfo* pInfo)
IntInfoAddStringModule(pInfo, _T("Using a flip-model swap chain"));
}
if (nSwapChainFlags & FBNEO_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) {
IntInfoAddStringModule(pInfo, _T("Variable refresh rate (tearing) presents enabled"));
} else if (bVidVRR) {
IntInfoAddStringModule(pInfo, _T("Variable refresh rate requested, but not supported"));
}
if (nVidFullscreen) {
if (bChangedDisplayMode) {
_sntprintf(szString, MAX_PATH, _T("Fullscreen (%i x %i, display mode changed)"), nVidScrnWidth, nVidScrnHeight);