mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 20:18:20 -04:00
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:
parent
aa4d9840c1
commit
3ebdf506bd
|
@ -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
4
go.mod
|
@ -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
2
go.sum
|
@ -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=
|
||||
|
|
|
@ -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}))
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue