mirror of
https://github.com/libretro/FBNeo.git
synced 2026-11-07 11:06:01 -10:00
improve framepacing with video sync
add vrr support
This commit is contained in:
1 parent
56db8a4fb4
commit
d2bb7d3475
9 files changed
+122
-2
No files matched your search
@@ -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
|
||||
|
||||
@@ -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"));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user