diff --git a/configs/config.yaml b/configs/config.yaml index 8ef789bf..8174f61f 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -174,6 +174,13 @@ emulator: # - isGlAllowed (bool) # - usesLibCo (bool) # - hasMultitap (bool) + # - vfr (bool) + # (experimental) + # Enable variable frame rate only for cores that can't produce a constant frame rate. + # By default, we assume that cores output frames at a constant rate which equals + # their tick rate (1/system FPS), but OpenGL cores like N64 may have significant + # frame rendering time inconsistencies. In general, VFR for CFR cores leads to + # noticeable video stutter (with the current frame rendering time calculations). list: gba: lib: mgba_libretro @@ -203,6 +210,7 @@ emulator: roms: [ "n64", "v64", "z64" ] isGlAllowed: true usesLibCo: true + vfr: true encoder: audio: diff --git a/pkg/config/emulator/config.go b/pkg/config/emulator/config.go index 34aad237..17aaee6d 100644 --- a/pkg/config/emulator/config.go +++ b/pkg/config/emulator/config.go @@ -59,19 +59,18 @@ type LibretroRepoConfig struct { } type LibretroCoreConfig struct { - Lib string - Config string - Roms []string - Folder string - Width int - Height int - IsGlAllowed bool - UsesLibCo bool - HasMultitap bool - AltRepo bool - - // hack: keep it here to pass it down the emulator - AutoGlContext bool + AltRepo bool + AutoGlContext bool // hack: keep it here to pass it down the emulator + Config string + Folder string + HasMultitap bool + Height int + IsGlAllowed bool + Lib string + Roms []string + UsesLibCo bool + VFR bool + Width int } type CoreInfo struct { diff --git a/pkg/worker/emulator/emulator.go b/pkg/worker/emulator/emulator.go index 4cc9a87d..256f92d8 100644 --- a/pkg/worker/emulator/emulator.go +++ b/pkg/worker/emulator/emulator.go @@ -54,6 +54,7 @@ type Metadata struct { UsesLibCo bool AutoGlContext bool HasMultitap bool + HasVFR bool } type ( diff --git a/pkg/worker/emulator/libretro/frontend.go b/pkg/worker/emulator/libretro/frontend.go index 4c550f69..aa9e07d9 100644 --- a/pkg/worker/emulator/libretro/frontend.go +++ b/pkg/worker/emulator/libretro/frontend.go @@ -108,12 +108,13 @@ func (f *Frontend) LoadMetadata(emu string) { config := f.conf.GetLibretroCoreConfig(emu) f.mu.Lock() coreLoad(emulator.Metadata{ - LibPath: config.Lib, - ConfigPath: config.Config, - IsGlAllowed: config.IsGlAllowed, - UsesLibCo: config.UsesLibCo, - HasMultitap: config.HasMultitap, AutoGlContext: config.AutoGlContext, + ConfigPath: config.Config, + HasMultitap: config.HasMultitap, + HasVFR: config.VFR, + IsGlAllowed: config.IsGlAllowed, + LibPath: config.Lib, + UsesLibCo: config.UsesLibCo, }) f.mu.Unlock() } diff --git a/pkg/worker/emulator/libretro/nanoarch.go b/pkg/worker/emulator/libretro/nanoarch.go index 86f22caf..9e9189f7 100644 --- a/pkg/worker/emulator/libretro/nanoarch.go +++ b/pkg/worker/emulator/libretro/nanoarch.go @@ -69,6 +69,8 @@ var ( libretroLogger = logger.Default() sdlCtx *graphics.SDL usesLibCo bool + hasVFR bool + tickTime int64 cSaveDirectory *C.char cSystemDirectory *C.char cUserName *C.char @@ -113,7 +115,11 @@ func coreVideoRefresh(data unsafe.Pointer, width, height uint, packed uint) { // this is useful only for cores with variable framerate, for the fixed framerate cores this adds stutter // !to find docs on Libretro refresh sync and frame times t := time.Now().UnixNano() - dt := t - lastFrameTime + dt := tickTime + // override frame rendering with dynamic frame times + if hasVFR { + dt = t - lastFrameTime + } lastFrameTime = t // some cores can return nothing @@ -414,6 +420,7 @@ func coreLoad(meta emulator.Metadata) { nano.v.isGl = meta.IsGlAllowed usesLibCo = meta.UsesLibCo nano.v.autoGlContext = meta.AutoGlContext + hasVFR = meta.HasVFR coreConfig, err = ReadProperties(meta.ConfigPath) if err != nil { libretroLogger.Warn().Err(err).Msg("config scan has been failed") @@ -510,6 +517,11 @@ func LoadGame(path string) error { nano.sysAvInfo.timing.fps, nano.sysAvInfo.geometry.aspect_ratio, nano.sysAvInfo.timing.sample_rate, ) + tickTime = int64(time.Second / time.Duration(nano.sysAvInfo.timing.fps)) + if hasVFR { + libretroLogger.Info().Msgf("variable framerate (VFR) is enabled") + } + if nano.v.isGl { // flip Y coordinates of OpenGL setRotation(uint(image.Flip180))