Skip to content

Commit

Permalink
Make frametime pure stats pkg; remove frame wait
Browse files Browse the repository at this point in the history
With time.Sleep now being particularly low resolution on windows in
go (see golang/go#44343), waiting for the
rest of a frame after it's complete isn't viable.

I'm now basing my wait in my own programs on the OS asking for more
sound data / the sound buffer growing too full, which doesn't rely
on sleep resolution (thanks to using a channel that sleeps/wakes
based (ultimately) on an OS audio system callback)
  • Loading branch information
theinternetftw committed Jan 10, 2024
1 parent 457b4fc commit 2bce0af
Showing 1 changed file with 49 additions and 59 deletions.
108 changes: 49 additions & 59 deletions frametimer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,97 +5,87 @@ import (
"time"
)

const frameTimeLogSize = 300
const timeLogSize = 300

// FrameTimer allows for delaying at the end of
// a frame, and for checking frame stats
type FrameTimer struct {
frameCount uint
rtimeLog []time.Duration
ftimeLog []time.Duration
frameCount uint
renderTimeLog []time.Duration
frameTimeLog []time.Duration

statsTimer int

lastFlipTime time.Time
lastRenderTime time.Time
}

// Makes a frame timer
func MakeFrameTimer() FrameTimer {
return FrameTimer{
lastFlipTime: time.Now(),
}
}

// WaitForFrametime sleeps until the frametime matches the desired interval
func (f *FrameTimer) WaitForFrametime(timeGoalSecs float64) {

f.MarkRenderComplete()

frametimeGoal := time.Duration(float64(time.Second) * timeGoalSecs)

rDiff := time.Now().Sub(f.lastFlipTime)

// hack to get better accuracy, could do
// a two stage wait with spin at the end,
// but that really adds to the cycles
fudge := 2 * time.Millisecond
toWait := frametimeGoal - rDiff - fudge
if toWait > time.Duration(0) {
<-time.NewTimer(toWait).C
}

f.MarkFrameComplete()
}

// TickFrame updates frame render stats as if the render was just completed
func (f *FrameTimer) MarkRenderComplete() {
rdiff := time.Now().Sub(f.lastFlipTime)
f.rtimeLog = append(f.rtimeLog, rdiff)
if len(f.rtimeLog) > frameTimeLogSize {
f.rtimeLog = f.rtimeLog[1:]
f.renderTimeLog = append(f.renderTimeLog, rdiff)
if len(f.renderTimeLog) > timeLogSize {
f.renderTimeLog = f.renderTimeLog[1:]
}
}

// TickFrame updates frame render stats as if the render was just completed
func (f *FrameTimer) MarkFrameComplete() {
frameStart := f.lastFlipTime
f.lastFlipTime = time.Now()

fdiff := time.Now().Sub(frameStart)
f.ftimeLog = append(f.ftimeLog, fdiff)
if len(f.ftimeLog) > frameTimeLogSize {
f.ftimeLog = f.ftimeLog[1:]
fdiff := f.lastFlipTime.Sub(frameStart)
f.frameTimeLog = append(f.frameTimeLog, fdiff)
if len(f.frameTimeLog) > timeLogSize {
f.frameTimeLog = f.frameTimeLog[1:]
}
f.frameCount++
f.statsTimer++
}

// PrintStatsEveryXFrames prints to stdout the presumed render time,
// as well as the full frame time after sleep. The argument
// determines how often (in frames) to issue a printout.
func (f *FrameTimer) PrintStatsEveryXFrames(numFrames int) {
if f.statsTimer >= numFrames {
type FrameStats struct {
MeanRenderTime time.Duration
MaxRenderTime time.Duration
MaxFrameTime time.Duration
MeanFrameTime time.Duration
}

func (f *FrameTimer) ResetStats() {
f.frameTimeLog = f.frameTimeLog[:0]
f.renderTimeLog = f.renderTimeLog[:0]
}

var maxRTime time.Duration
var maxFTime time.Duration
var meanRTime time.Duration
var meanFTime time.Duration
for _, d := range f.rtimeLog {
if d > maxRTime {
maxRTime = d
}
meanRTime += d
func (f *FrameTimer) GetStats() FrameStats {
stats := FrameStats{}
for _, d := range f.renderTimeLog {
if d > stats.MaxRenderTime {
stats.MaxRenderTime = d
}
for _, d := range f.ftimeLog {
if d > maxFTime {
maxFTime = d
}
meanFTime += d
stats.MeanRenderTime += d
}
for _, d := range f.frameTimeLog {
if d > stats.MaxFrameTime {
stats.MaxFrameTime = d
}
meanRTime /= time.Duration(len(f.rtimeLog))
meanFTime /= time.Duration(len(f.ftimeLog))
fmt.Printf("meanRTime %.4f, meanFTime %.4f, maxRTime %.4f, maxFTime %.4f\n", meanRTime.Seconds(), meanFTime.Seconds(), maxRTime.Seconds(), maxFTime.Seconds())
stats.MeanFrameTime += d
}
stats.MeanRenderTime /= time.Duration(len(f.renderTimeLog))
stats.MeanFrameTime /= time.Duration(len(f.frameTimeLog))
return stats
}

// PrintStatsEveryXFrames prints to stdout the recorded mean/max render time,
// as well as the same for full frame time. The argument determines how often
// (in frames) to issue a printout. (Mean/max are for the past timeLogSize frames)
func (f *FrameTimer) PrintStatsEveryXFrames(numFrames int) {
if f.statsTimer >= numFrames {
stats := f.GetStats()

fmt.Printf("meanRender %.4f, meanFrame %.4f, maxRender %.4f, maxFrame %.4f\n",
stats.MeanRenderTime.Seconds(), stats.MeanFrameTime.Seconds(),
stats.MeanFrameTime.Seconds(), stats.MaxFrameTime.Seconds())
f.statsTimer = 0
}
}

0 comments on commit 2bce0af

Please sign in to comment.