FPS slider in control window

requires some work, at the moment the limiter works whenever a new frame
is triggered, this is fine for high frame rate but it breaks down at
very low frame rates

updated DrawList function to use new PackedColor type
This commit is contained in:
JetSetIlly 2020-02-22 13:04:54 +00:00
parent aa4d9840c1
commit 3ebdf506bd
12 changed files with 120 additions and 76 deletions

View file

@ -79,10 +79,14 @@ func (t *mockTV) SpecIDOnCreation() string {
func (t *mockTV) SetFPSCap(set bool) {
}
func (t *mockTV) SetFPS(fps int) {
func (t *mockTV) ReqFPS(fps float32) {
}
func (t *mockTV) GetFPS() float64 {
func (t *mockTV) GetActualFPS() float32 {
return 0.0
}
func (t *mockTV) GetReqFPS() float32 {
return 0.0
}

4
go.mod
View file

@ -6,12 +6,12 @@ require (
github.com/go-audio/audio v1.0.0
github.com/go-audio/wav v1.0.0
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7
github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200210203827-9487e0b50076
github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200222162349-d2960522c721
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942
github.com/veandco/go-sdl2 v0.3.3
golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab // indirect
)
replace github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200210203827-9487e0b50076 => github.com/JetSetIlly/imgui-go/v2 v2.1.2-0.20200220143851-7c9457a250cb
//replace github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200210203827-9487e0b50076 => github.com/JetSetIlly/imgui-go/v2 v2.1.2-0.20200220143851-7c9457a250cb
//replace github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200210203827-9487e0b50076 => ../imgui-go

2
go.sum
View file

@ -19,6 +19,8 @@ github.com/inkyblackness/imgui-go v1.12.0 h1:uaxSM5SbbqCTGEx5ig7B2J78hM3g3az4f5N
github.com/inkyblackness/imgui-go v1.12.0/go.mod h1:S9wTWrw/HfxYPbOnqsbck9A6mxHRauv+Sy+bz5+BQwc=
github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200210203827-9487e0b50076 h1:0x7PVVOuHYmiOB7w1AJ5bmeINIspH1NHGsdKU6fQpVI=
github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200210203827-9487e0b50076/go.mod h1:cwQKd6U2onyuT5qwSC3DKCRsUE1SQ58TSVVpLoW/pDs=
github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200222162349-d2960522c721 h1:TJpqOBAVJTKJrn2bI1+jM/ApZIRFOeLW63FfVWS1rIs=
github.com/inkyblackness/imgui-go/v2 v2.1.2-0.20200222162349-d2960522c721/go.mod h1:cwQKd6U2onyuT5qwSC3DKCRsUE1SQ58TSVVpLoW/pDs=
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942 h1:A7GG7zcGjl3jqAqGPmcNjd/D9hzL95SuoOQAaFNdLU0=
github.com/pkg/term v0.0.0-20190109203006-aa71e9d9e942/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

View file

@ -37,7 +37,7 @@ func minFrameDimension(s string, t ...string) imgui.Vec2 {
// draw toggle button at current cursor position
func toggleButton(id string, v *bool, col imgui.Vec4) {
bg := colorConvertFloat4ToU32(col)
bg := imgui.PackedColorFromVec4(col)
p := imgui.CursorScreenPos()
dl := imgui.WindowDrawList()
@ -72,27 +72,5 @@ func toggleButton(id string, v *bool, col imgui.Vec4) {
dl.AddRectFilledV(p, imgui.Vec2{p.X + width, p.Y + height}, bg, radius, imgui.DrawCornerFlagsAll)
dl.AddCircleFilled(imgui.Vec2{p.X + radius + t*(width-radius*2.0), p.Y + radius},
radius-1.5, colorConvertFloat4ToU32(imgui.Vec4{1.0, 1.0, 1.0, 1.0}))
}
func float32ToUint32(f float32) uint32 {
s := f
if s < 0.0 {
s = 0.0
} else if s > 1.0 {
s = 1.0
}
return uint32(f*255.0 + 0.5)
}
// ColorConvertFloat4ToU32 converts a color represented by a four-dimensional
// vector to an unsigned 32bit integer.
func colorConvertFloat4ToU32(col imgui.Vec4) uint32 {
var r uint32
r = float32ToUint32(col.X) << 0
r |= float32ToUint32(col.Y) << 8
r |= float32ToUint32(col.Z) << 16
r |= float32ToUint32(col.W) << 24
return r
radius-1.5, imgui.PackedColorFromVec4(imgui.Vec4{1.0, 1.0, 1.0, 1.0}))
}

View file

@ -37,6 +37,7 @@ type winControl struct {
img *SdlImgui
videoStep bool
fps float32
// widget dimensions
stepButtonDim imgui.Vec2
@ -51,6 +52,7 @@ func newWinControl(img *SdlImgui) (managedWindow, error) {
func (win *winControl) init() {
win.stepButtonDim = minFrameDimension(videoCycleLabel, cpuInstructionLabel)
win.fps = win.img.vcs.TV.GetReqFPS()
}
func (win *winControl) destroy() {
@ -68,20 +70,20 @@ func (win *winControl) draw() {
imgui.SetNextWindowPosV(imgui.Vec2{645, 253}, imgui.ConditionFirstUseEver, imgui.Vec2{0, 0})
imgui.BeginV(winControlTitle, &win.open, imgui.WindowFlagsAlwaysAutoResize)
w := minFrameDimension("Run", "Halt")
dim := minFrameDimension("Run", "Halt")
if win.img.paused {
imgui.PushStyleColor(imgui.StyleColorButton, win.img.cols.ControlRun)
imgui.PushStyleColor(imgui.StyleColorButtonHovered, win.img.cols.ControlRunHovered)
imgui.PushStyleColor(imgui.StyleColorButtonActive, win.img.cols.ControlRunActive)
if imgui.ButtonV("Run", w) {
if imgui.ButtonV("Run", dim) {
win.img.issueTermCommand("RUN")
}
} else {
imgui.PushStyleColor(imgui.StyleColorButton, win.img.cols.ControlHalt)
imgui.PushStyleColor(imgui.StyleColorButtonHovered, win.img.cols.ControlHaltHovered)
imgui.PushStyleColor(imgui.StyleColorButtonActive, win.img.cols.ControlHaltActive)
if imgui.ButtonV("Halt", w) {
if imgui.ButtonV("Halt", dim) {
win.img.issueTermCommand("HALT")
}
}
@ -101,6 +103,28 @@ func (win *winControl) draw() {
win.img.issueTermCommand("STEP SCANLINE")
}
imgui.Spacing()
// figuring the width of fps slider requires some care. we need to take
// into account the width of the label and of the padding and inner
// spacing.
w := imgui.WindowWidth()
w -= (imgui.CurrentStyle().FramePadding().X * 2) + (imgui.CurrentStyle().ItemInnerSpacing().X * 2)
w -= imgui.CalcTextSize("FPS", false, 0).X
imgui.PushItemWidth(w)
// fps slider
win.fps = win.img.vcs.TV.GetReqFPS()
if imgui.SliderFloatV("FPS", &win.fps, 0.1, 100, "%.1f", 1.0) {
win.img.vcs.TV.ReqFPS(win.fps)
}
imgui.PopItemWidth()
// reset to specifcation rate on right mouse click
if imgui.IsItemHoveredV(imgui.HoveredFlagsAllowWhenDisabled) && imgui.IsMouseDown(1) {
win.img.vcs.TV.ReqFPS(-1)
}
imgui.End()
}

View file

@ -45,9 +45,9 @@ type winDisasm struct {
pcPrevFrame uint16
// gutter colors
colCurrentPC uint32
colBreakpoint uint32
colMnemonic uint32
colCurrentPC imgui.PackedColor
colBreakpoint imgui.PackedColor
colMnemonic imgui.PackedColor
}
func newWinDisasm(img *SdlImgui) (managedWindow, error) {
@ -60,9 +60,9 @@ func newWinDisasm(img *SdlImgui) (managedWindow, error) {
}
func (win *winDisasm) init() {
win.colCurrentPC = colorConvertFloat4ToU32(win.img.cols.DisasmCurrentPC)
win.colBreakpoint = colorConvertFloat4ToU32(win.img.cols.DisasmAddress)
win.colBreakpoint = colorConvertFloat4ToU32(win.img.cols.DisasmAddress)
win.colCurrentPC = imgui.PackedColorFromVec4(win.img.cols.DisasmCurrentPC)
win.colBreakpoint = imgui.PackedColorFromVec4(win.img.cols.DisasmAddress)
win.colBreakpoint = imgui.PackedColorFromVec4(win.img.cols.DisasmAddress)
}
func (win *winDisasm) destroy() {
@ -244,7 +244,7 @@ const (
fillFull
)
func (win *winDisasm) drawGutter(fill fillType, col uint32) {
func (win *winDisasm) drawGutter(fill fillType, col imgui.PackedColor) {
r := imgui.FrameHeight() / 4
p := imgui.CursorScreenPos()
p.Y -= r * 2

View file

@ -136,7 +136,12 @@ func (win *winScreen) draw() {
if win.img.paused {
imgui.Text("no fps")
} else {
imgui.Text(fmt.Sprintf("%03.1f fps", win.img.vcs.TV.GetFPS()))
fps := win.img.vcs.TV.GetActualFPS()
if fps < 1.0 {
imgui.Text("< 1 fps")
} else {
imgui.Text(fmt.Sprintf("%03.1f fps", win.img.vcs.TV.GetActualFPS()))
}
}
}

View file

@ -149,9 +149,8 @@ func (wm *windowManager) destroy() {
}
func (wm *windowManager) drawWindows() {
wm.init()
if wm.img.vcs != nil && wm.img.dsm != nil {
wm.init()
wm.drawMainMenu()
for w := range wm.windows {
wm.windows[w].draw()

View file

@ -46,53 +46,73 @@ type FpsLimiter struct {
// toggle limited on and off
Active bool
framesPerSecond int
RequestedFPS float32
secondsPerFrame time.Duration
tick chan bool
tickNow chan bool
trackFrameCt uint32
FPS float64
ActualFPS float32
}
// NewFPSLimiter is the preferred method of initialisation for FpsLimiter type
func NewFPSLimiter(framesPerSecond int) *FpsLimiter {
func NewFPSLimiter(RequestedFPS float32) *FpsLimiter {
lim := &FpsLimiter{Active: true}
lim.SetLimit(framesPerSecond)
lim.tick = make(chan bool)
lim.tickNow = make(chan bool)
// run ticker concurrently
go func() {
adjustedSecondPerFrame := lim.secondsPerFrame
t := time.Now()
rateTimer := time.NewTimer(lim.secondsPerFrame)
for {
lim.tick <- true
time.Sleep(adjustedSecondPerFrame)
nt := time.Now()
adjustedSecondPerFrame -= nt.Sub(t) - lim.secondsPerFrame
t = nt
select {
case <-rateTimer.C:
case <-lim.tickNow:
rateTimer.Stop()
}
rateTimer.Reset(lim.secondsPerFrame)
}
}()
// fun fps calculator concurrently
go func() {
dur, _ := time.ParseDuration("0.5s")
for {
time.Sleep(dur)
t := time.Now()
frames := float64(atomic.LoadUint32(&lim.trackFrameCt))
lim.FPS = frames / dur.Seconds()
updateRate, _ := time.ParseDuration("0.5s")
for {
// wait for spcified duration
time.Sleep(updateRate)
// acutal end time
et := time.Now()
// calculate actual rate
frames := float32(atomic.LoadUint32(&lim.trackFrameCt))
lim.ActualFPS = frames / float32(et.Sub(t).Seconds())
atomic.StoreUint32(&lim.trackFrameCt, 0)
// new start time
t = et
}
}()
lim.RequestedFPS = RequestedFPS
lim.secondsPerFrame, _ = time.ParseDuration(fmt.Sprintf("%fs", float32(1.0)/float32(RequestedFPS)))
return lim
}
// SetLimit changes the limit at which the FpsLimiter waits
func (lim *FpsLimiter) SetLimit(framesPerSecond int) {
lim.framesPerSecond = framesPerSecond
lim.secondsPerFrame, _ = time.ParseDuration(fmt.Sprintf("%fs", float64(1.0)/float64(framesPerSecond)))
// SetFPS changes the limit at which the FpsLimiter waits
func (lim *FpsLimiter) SetFPS(RequestedFPS float32) {
lim.RequestedFPS = RequestedFPS
lim.secondsPerFrame, _ = time.ParseDuration(fmt.Sprintf("%fs", float32(1.0)/float32(RequestedFPS)))
select {
case lim.tickNow <- true:
default:
}
}
// Wait will block until trigger

View file

@ -73,13 +73,20 @@ type Television interface {
// Set whether the emulation should wait for FPS limiter
SetFPSCap(set bool)
// Set the frames per second. This overrides the frame rate of the
// specification. A negative FPS value restores the specifcications frame
// rate.
SetFPS(fps int)
// Request the number frames per second. This overrides the frame rate of
// the specification. A negative FPS value restores the specifcications
// frame rate.
//
// Note that this is only a request, the emulation may not be able to
// achieve that rate.
ReqFPS(fps float32)
// The current number of frames per second
GetFPS() float64
GetActualFPS() float32
// The requested number of frames per second. Compare with GetActualFPS()
// to check for accuracy.
GetReqFPS() float32
}
// PixelRenderer implementations displays, or otherwise works with, visual

View file

@ -60,7 +60,7 @@ type Specification struct {
ScanlineBottom int
// the number of frames per second required by the specification
FramesPerSecond int
FramesPerSecond float32
// AspectBias transforms the scaling factor for the X axis. in other words,
// for width of every pixel is height of every pixel multiplied by the
@ -102,7 +102,7 @@ func init() {
ScanlinesVisible: 192,
ScanlinesOverscan: 30,
ScanlinesTotal: 262,
FramesPerSecond: 60,
FramesPerSecond: 60.0,
AspectBias: 0.91,
}
@ -117,7 +117,7 @@ func init() {
ScanlinesVisible: 228,
ScanlinesOverscan: 36,
ScanlinesTotal: 312,
FramesPerSecond: 50,
FramesPerSecond: 50.0,
AspectBias: 1.09,
}

View file

@ -423,7 +423,7 @@ func (tv *television) newFrame() error {
// change fps
if tv.fpsFromSpec {
tv.lmtr.SetLimit(tv.spec.FramesPerSecond)
tv.lmtr.SetFPS(tv.spec.FramesPerSecond)
}
}
@ -533,17 +533,22 @@ func (tv *television) SetFPSCap(set bool) {
}
// SetFPS implements the Television interface
func (tv *television) SetFPS(fps int) {
func (tv *television) ReqFPS(fps float32) {
if fps < 0 {
tv.lmtr.SetLimit(tv.spec.FramesPerSecond)
tv.lmtr.SetFPS(tv.spec.FramesPerSecond)
tv.fpsFromSpec = true
} else {
tv.lmtr.SetLimit(fps)
tv.lmtr.SetFPS(fps)
tv.fpsFromSpec = false
}
}
// GetFPS implements the Television interface
func (tv *television) GetFPS() float64 {
return tv.lmtr.FPS
// GetActualFPS implements the Television interface
func (tv *television) GetActualFPS() float32 {
return tv.lmtr.ActualFPS
}
// GetReqFPS implements the Television interface
func (tv *television) GetReqFPS() float32 {
return tv.lmtr.RequestedFPS
}