Switch to the more precise runtime.nanotime for frame timing

This commit is contained in:
sergystepanov 2026-07-02 00:11:27 +03:00
parent f2cb7ebc7a
commit 68ac6afab4
2 changed files with 25 additions and 15 deletions

9
pkg/os/nanotime.go Normal file
View file

@ -0,0 +1,9 @@
package os
import _ "unsafe"
//go:linkname nanotime runtime.nanotime
func nanotime() int64
// Nanotime returns a monotonic timestamp in nanoseconds.
func Nanotime() int64 { return nanotime() }

View file

@ -320,17 +320,17 @@ func (f *Frontend) Start() {
// The main loop of Libretro
targetFrameTime := time.Duration(float64(time.Second) / f.nano.VideoFramerate())
targetFrameTimeNs := int64(float64(time.Second) / f.nano.VideoFramerate())
// stop sleeping and start spinning in the remaining 1ms
const spinThreshold = 1 * time.Millisecond
const spinThresholdNs = int64(1 * time.Millisecond)
// how many frames will be considered not normal
const lateFramesThreshold = 3
batch := max(f.SpinBatch, 1)
lastFrameStart := time.Now()
lastFrameStart := os.Nanotime()
for {
select {
@ -339,27 +339,28 @@ func (f *Frontend) Start() {
default:
f.Tick()
elapsed := time.Since(lastFrameStart)
sleepTime := targetFrameTime - elapsed
deadline := lastFrameStart.Add(targetFrameTime)
now := os.Nanotime()
elapsed := now - lastFrameStart
sleepTimeNs := targetFrameTimeNs - elapsed
deadline := lastFrameStart + targetFrameTimeNs
if sleepTime > 0 {
if sleepTimeNs > 0 {
// SLEEP
// if we have plenty of time, sleep to save CPU and
// wake up slightly before the target time
if sleepTime > spinThreshold {
time.Sleep(sleepTime - spinThreshold)
if sleepTimeNs > spinThresholdNs {
time.Sleep(time.Duration(sleepTimeNs - spinThresholdNs))
}
// SPIN
// if we are close to the target,
// burn CPU and check the clock with ns resolution.
// SpinBatch batches the time check to reduce
// syscall overhead while keeping sub-us precision.
// overhead while keeping sub-us precision.
for {
for range batch - 1 {
}
if !time.Now().Before(deadline) {
if os.Nanotime() >= deadline {
break
}
}
@ -368,7 +369,7 @@ func (f *Frontend) Start() {
// lagging behind the target framerate so we don't sleep
if f.conf.LogDroppedFrames {
// !to make some stats counter instead
f.log.Debug().Msgf("[] Frame drop: %v", elapsed)
f.log.Debug().Msgf("[] Frame drop: %v", time.Duration(elapsed))
}
f.skipVideo = true
}
@ -378,13 +379,13 @@ func (f *Frontend) Start() {
// adding targetFrameTime to the previous start
// prevents drift, if one frame was late,
// we try to catch up in the next frame
lastFrameStart = lastFrameStart.Add(targetFrameTime)
lastFrameStart = lastFrameStart + targetFrameTimeNs
// if execution was paused or heavily delayed,
// reset lastFrameStart so we don't try to run
// a bunch of frames instantly to catch up
if time.Since(lastFrameStart) > targetFrameTime*lateFramesThreshold {
lastFrameStart = time.Now()
if os.Nanotime()-lastFrameStart > targetFrameTimeNs*lateFramesThreshold {
lastFrameStart = os.Nanotime()
}
}
}