mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 12:08:01 -04:00
timeline window now runs the rewind.GetState()/thumbnailer in the main emulation goroutine
the thumbnailer does the actual emulation in a new goroutine so there's no lag here but it's necessary to PushRawEvent() so that rewind.GetState() doesn't race
This commit is contained in:
parent
eecd5aa8c1
commit
249e3c4b60
4
Makefile
4
Makefile
|
@ -3,13 +3,13 @@ compileFlags = '-c 3 -B -wb=false'
|
|||
# profilingRom = roms/Homebrew/hs_2600.bin
|
||||
# profilingRom = roms/Homebrew/CDF/galaga_dmo_v2_NTSC.bin
|
||||
# profilingRom = roms/Homebrew/DPC+ARM/ZaxxonHDDemo_150927_NTSC.bin
|
||||
# profilingRom = roms/Rsboxing.bin
|
||||
profilingRom = roms/Rsboxing.bin
|
||||
# profilingRom = "test_roms/plusrom/sokoboo Plus.bin"
|
||||
# profilingRom = "roms/starpath/02 - Communist Mutants From Space (Ntsc).mp3"
|
||||
# profilingRom = "roms/The Official Frogger.bin"
|
||||
# profilingRom = roms/Homebrew/CDF/gorfarc_20201231_demo1_NTSC.bin
|
||||
# profilingRom = roms/Pitfall.bin
|
||||
profilingRom =
|
||||
# profilingRom =
|
||||
|
||||
.PHONY: all clean tidy generate check_lint lint check_glsl glsl_validate check_pandoc readme_spell test race race_debug profile profile_cpu profile_cpu_again profile_mem_debug profile_trace build_assertions build check_upx release release_statsview cross_windows cross_windows_statsview binaries check_gotip build_with_gotip
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ func newLazyRewind(val *LazyValues) *LazyRewind {
|
|||
|
||||
func (lz *LazyRewind) push() {
|
||||
lz.timeline.Store(lz.val.dbg.Rewind.GetTimeline())
|
||||
lz.comparison.Store(lz.val.dbg.Rewind.GetComparison())
|
||||
lz.comparison.Store(lz.val.dbg.Rewind.GetComparisonState())
|
||||
}
|
||||
|
||||
func (lz *LazyRewind) update() {
|
||||
|
|
|
@ -17,7 +17,6 @@ package sdlimgui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -52,7 +51,6 @@ type winSelectROM struct {
|
|||
controlHeight float32
|
||||
|
||||
thmb *thumbnailer.Thumbnailer
|
||||
thmbReceive chan *image.RGBA
|
||||
thmbTexture uint32
|
||||
}
|
||||
|
||||
|
@ -67,7 +65,6 @@ func newFileSelector(img *SdlImgui) (window, error) {
|
|||
|
||||
var err error
|
||||
|
||||
// create a new thumbnailer instance
|
||||
win.thmb, err = thumbnailer.NewThumbnailer()
|
||||
if err != nil {
|
||||
return nil, curated.Errorf("debugger: %v", err)
|
||||
|
@ -127,8 +124,9 @@ func (win *winSelectROM) setOpen(open bool) {
|
|||
}
|
||||
|
||||
func (win *winSelectROM) draw() {
|
||||
// receive new thumbnail data and copy to texture
|
||||
select {
|
||||
case img := <-win.thmbReceive:
|
||||
case img := <-win.thmb.Render:
|
||||
if img != nil {
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, int32(img.Stride)/4)
|
||||
defer gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
||||
|
@ -335,5 +333,5 @@ func (win *winSelectROM) setSelectedFile(filename string) {
|
|||
}
|
||||
cartload.EmulationLabel = thumbnailer.EmulationLabel
|
||||
|
||||
win.thmbReceive = win.thmb.CreateFromLoader(cartload, thumbnailer.UndefinedNumFrames)
|
||||
win.thmb.CreateFromLoader(cartload, thumbnailer.UndefinedNumFrames)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ package sdlimgui
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
|
||||
"github.com/go-gl/gl/v3.2-core/gl"
|
||||
"github.com/inkyblackness/imgui-go/v4"
|
||||
|
@ -35,11 +34,11 @@ type winTimeline struct {
|
|||
// whether the rewind "slider" is active
|
||||
rewindingActive bool
|
||||
|
||||
// thumbnailer will be using emulation states created in the main emulation
|
||||
// goroutine so we must thumbnail those states in the same goroutine.
|
||||
thmb *thumbnailer.Thumbnailer
|
||||
thmbReceive chan *image.RGBA
|
||||
thmbTexture uint32
|
||||
|
||||
thumbnailFrame int
|
||||
thmbFrame int
|
||||
}
|
||||
|
||||
func newWinTimeline(img *SdlImgui) (window, error) {
|
||||
|
@ -49,7 +48,6 @@ func newWinTimeline(img *SdlImgui) (window, error) {
|
|||
|
||||
var err error
|
||||
|
||||
// create a new thumbnailer instance
|
||||
win.thmb, err = thumbnailer.NewThumbnailer()
|
||||
if err != nil {
|
||||
return nil, curated.Errorf("debugger: %v", err)
|
||||
|
@ -79,8 +77,9 @@ func (win *winTimeline) setOpen(open bool) {
|
|||
}
|
||||
|
||||
func (win *winTimeline) draw() {
|
||||
// receive new thumbnail data and copy to texture
|
||||
select {
|
||||
case img := <-win.thmbReceive:
|
||||
case img := <-win.thmb.Render:
|
||||
if img != nil {
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, int32(img.Stride)/4)
|
||||
defer gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
||||
|
@ -171,7 +170,7 @@ func (win *winTimeline) drawTimeline() {
|
|||
|
||||
// scanline trace
|
||||
traceSize = imgui.Vec2{X: availableWidth, Y: 50}
|
||||
imgui.BeginChildV("##timelinescanlinetrace", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
imgui.BeginChildV("##timelinetrace", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
pos = imgui.CursorScreenPos()
|
||||
|
||||
x := pos.X
|
||||
|
@ -244,7 +243,7 @@ func (win *winTimeline) drawTimeline() {
|
|||
// input trace
|
||||
// TODO: right player and panel input
|
||||
traceSize = imgui.Vec2{X: availableWidth, Y: inputHeight}
|
||||
imgui.BeginChildV("##timelineinputtrace", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
imgui.BeginChildV("##timelinetrace_input", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
pos = imgui.CursorScreenPos()
|
||||
x = pos.X
|
||||
y := pos.Y
|
||||
|
@ -263,7 +262,7 @@ func (win *winTimeline) drawTimeline() {
|
|||
|
||||
// rewind range indicator
|
||||
traceSize = imgui.Vec2{X: availableWidth, Y: rangeHeight}
|
||||
imgui.BeginChildV("##timelineindicators", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
imgui.BeginChildV("##timelinetrace_indicators", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
pos = imgui.CursorScreenPos()
|
||||
|
||||
dl.AddRectFilled(imgui.Vec2{X: pos.X + float32((timeline.AvailableStart-rewindOffset)*traceWidth), Y: pos.Y},
|
||||
|
@ -274,7 +273,7 @@ func (win *winTimeline) drawTimeline() {
|
|||
|
||||
// frame indicators
|
||||
traceSize = imgui.Vec2{X: availableWidth, Y: frameIndicatorRadius}
|
||||
imgui.BeginChildV("##timelinecurrent", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
imgui.BeginChildV("##timelinetrace_current", traceSize, false, imgui.WindowFlagsNoMove)
|
||||
pos = imgui.CursorScreenPos()
|
||||
|
||||
// comparison frame indicator
|
||||
|
@ -309,7 +308,8 @@ func (win *winTimeline) drawTimeline() {
|
|||
rewindEndFrame := win.img.lz.Rewind.Timeline.AvailableEnd
|
||||
rewindHoverFrame := int(hoverX/traceWidth) + rewindOffset
|
||||
|
||||
// rewind "slider" is attached to scanline trace
|
||||
// hover and clicking works on the group
|
||||
|
||||
if imgui.IsMouseDown(0) && (hovered || win.rewindingActive) {
|
||||
win.rewindingActive = true
|
||||
|
||||
|
@ -380,9 +380,15 @@ func (win *winTimeline) drawTimeline() {
|
|||
imgui.TableNextColumn()
|
||||
|
||||
// selecting the correct thumbnail requires different indexing than the timline
|
||||
if win.thumbnailFrame != rewindHoverFrame {
|
||||
win.thmbReceive = win.thmb.CreateFromState(win.img.dbg.Rewind.GetState(rewindHoverFrame), 1)
|
||||
win.thumbnailFrame = rewindHoverFrame
|
||||
if win.thmbFrame != rewindHoverFrame {
|
||||
win.thmbFrame = rewindHoverFrame
|
||||
|
||||
// Rewind.GetState must be run in the emulation thread. CreateFromState doesn't need to be but
|
||||
// because the thumbnailer runs in its own goroutine there's no real time penalty on the main
|
||||
// emulation even when it is running
|
||||
win.img.dbg.PushRawEvent(func() {
|
||||
win.thmb.CreateFromState(win.img.dbg.Rewind.GetState(rewindHoverFrame), 1)
|
||||
})
|
||||
}
|
||||
|
||||
imgui.Image(imgui.TextureID(win.thmbTexture), imgui.Vec2{specification.ClksVisible * 3, specification.AbsoluteMaxScanlines}.Times(0.3))
|
||||
|
|
|
@ -214,3 +214,13 @@ func (vcs *VCS) SetClockSpeed(tvSpec string) error {
|
|||
}
|
||||
return curated.Errorf("vcs: cannot set clock speed for unknown tv specification (%s)", tvSpec)
|
||||
}
|
||||
|
||||
// DetatchEmulationExtras removes all possible monitors, recorders, etc. from
|
||||
// the emulation. Currently this mean: the TIA audio tracker, the RIOT event
|
||||
// recorders and playback, and RIOT plug monitor.
|
||||
func (vcs *VCS) DetatchEmulationExtras() {
|
||||
vcs.TIA.Audio.SetTracker(nil)
|
||||
vcs.RIOT.Ports.AttachEventRecorder(nil)
|
||||
vcs.RIOT.Ports.AttachPlayback(nil)
|
||||
vcs.RIOT.Ports.AttachPlugMonitor(nil)
|
||||
}
|
||||
|
|
|
@ -41,15 +41,6 @@
|
|||
// next snapshot is taken (meaning that there is only ever one execution state
|
||||
// in the history at any one time and that it will be at the end).
|
||||
//
|
||||
// Do not call RecordFrameState() or RecordExecutionState() between CPU
|
||||
// instruction boundaries - the program will intentionally panic if this is
|
||||
// attempted.
|
||||
//
|
||||
// Not being able to snapshot state in between CPU instructions isn't as
|
||||
// limiting as you may think. The Goto() function can be used to specify frame
|
||||
// coordinates to the video cycle level and will take care of finding the
|
||||
// nearest snapshot and "catching up" from there.
|
||||
//
|
||||
// Snapshots are stored in frame order from the splice point. The splice point
|
||||
// will be wherever the snapshot history has been rewound to. For example, in a
|
||||
// history of length 100 frames: the emulation has rewound back to frame 50.
|
||||
|
|
|
@ -286,13 +286,6 @@ func (r *Rewind) snapshot(level snapshotLevel) *State {
|
|||
return snapshot(r.vcs, level)
|
||||
}
|
||||
|
||||
// GetCurrentState returns a temporary snapshot of the current state.
|
||||
//
|
||||
// It is not added to the rewind history.
|
||||
func (r *Rewind) GetCurrentState() *State {
|
||||
return r.snapshot(levelTemporary)
|
||||
}
|
||||
|
||||
// RecordState should be called after every CPU instruction. A new state will
|
||||
// be recorded if the current rewind policy agrees.
|
||||
func (r *Rewind) RecordState() {
|
||||
|
@ -571,11 +564,6 @@ func (r *Rewind) SetComparison() {
|
|||
r.comparison = r.GetCurrentState()
|
||||
}
|
||||
|
||||
// GetComparison gets a reference to current comparison point.
|
||||
func (r *Rewind) GetComparison() *State {
|
||||
return r.comparison
|
||||
}
|
||||
|
||||
// NewFrame is in an implementation of television.FrameTrigger.
|
||||
func (r *Rewind) NewFrame(frameInfo television.FrameInfo) error {
|
||||
r.addTimelineEntry(frameInfo)
|
||||
|
@ -583,9 +571,30 @@ func (r *Rewind) NewFrame(frameInfo television.FrameInfo) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetState returns a copy for the nearest state for the indicated frame.
|
||||
func (r *Rewind) GetState(frame int) *State {
|
||||
// get nearest index of entry from which we can being to (re)generate the
|
||||
// current frame
|
||||
res := r.findFrameIndex(frame)
|
||||
return r.entries[res.nearestIdx]
|
||||
s := r.entries[res.nearestIdx]
|
||||
|
||||
// return copy of state
|
||||
return &State{
|
||||
TV: s.TV.Snapshot(),
|
||||
CPU: s.CPU.Snapshot(),
|
||||
Mem: s.Mem.Snapshot(),
|
||||
RIOT: s.RIOT.Snapshot(),
|
||||
TIA: s.TIA.Snapshot(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetCurrentState returns a temporary snapshot of the current state.
|
||||
func (r *Rewind) GetCurrentState() *State {
|
||||
return r.snapshot(levelTemporary)
|
||||
}
|
||||
|
||||
// GetComparisonState gets a reference to current comparison point. This is not
|
||||
// a copy of the state but the actual state.
|
||||
func (r *Rewind) GetComparisonState() *State {
|
||||
return r.comparison
|
||||
}
|
||||
|
|
20
thumbnailer/doc.go
Normal file
20
thumbnailer/doc.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package thumbnailer can be used to create a single thumbnail or a series of
|
||||
// thumbnail images with the CreateFromLoader() or CreateFromState() functions.
|
||||
//
|
||||
// Thumbnailers can be created to run synchronously or asynchronously.
|
||||
package thumbnailer
|
|
@ -13,8 +13,6 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package thumbnailer can be used to create a single thumbnail or a series of
|
||||
// thumbnail images with the Create() function.
|
||||
package thumbnailer
|
||||
|
||||
import (
|
||||
|
@ -49,7 +47,7 @@ type Thumbnailer struct {
|
|||
emulationQuit chan bool
|
||||
emulationCompleted chan bool
|
||||
|
||||
renderChannel chan *image.RGBA
|
||||
Render chan *image.RGBA
|
||||
}
|
||||
|
||||
// NewThumbnailer is the preferred method of initialisation for the Thumbnailer type.
|
||||
|
@ -57,7 +55,7 @@ func NewThumbnailer() (*Thumbnailer, error) {
|
|||
thmb := &Thumbnailer{
|
||||
emulationQuit: make(chan bool, 1),
|
||||
emulationCompleted: make(chan bool, 1),
|
||||
renderChannel: make(chan *image.RGBA, 1),
|
||||
Render: make(chan *image.RGBA, 1),
|
||||
}
|
||||
|
||||
// emulation has completed, by definition, on startup
|
||||
|
@ -80,16 +78,9 @@ func NewThumbnailer() (*Thumbnailer, error) {
|
|||
|
||||
thmb.img = image.NewRGBA(image.Rect(0, 0, specification.ClksScanline, specification.AbsoluteMaxScanlines))
|
||||
|
||||
// clear pixels. setting the alpha channel so we don't have to later (the
|
||||
// alpha channel never changes)
|
||||
for y := 0; y < thmb.img.Bounds().Size().Y; y++ {
|
||||
for x := 0; x < thmb.img.Bounds().Size().X; x++ {
|
||||
thmb.img.SetRGBA(x, y, color.RGBA{0, 0, 0, 255})
|
||||
}
|
||||
}
|
||||
|
||||
// start with a NTSC television as default
|
||||
thmb.Resize(television.NewFrameInfo(specification.SpecNTSC))
|
||||
thmb.Reset()
|
||||
|
||||
return thmb, nil
|
||||
}
|
||||
|
@ -128,9 +119,7 @@ const UndefinedNumFrames = -1
|
|||
// CreateFromLoader will cause images to be returned from a running emulation initialised
|
||||
// with the specified cartridge loader. The emulation will run for a number of
|
||||
// frames before ending.
|
||||
//
|
||||
// It returns the channel over which new frames will be sent.
|
||||
func (thmb *Thumbnailer) CreateFromLoader(loader cartridgeloader.Loader, numFrames int) chan *image.RGBA {
|
||||
func (thmb *Thumbnailer) CreateFromLoader(loader cartridgeloader.Loader, numFrames int) {
|
||||
thmb.wait()
|
||||
|
||||
go func() {
|
||||
|
@ -163,16 +152,14 @@ func (thmb *Thumbnailer) CreateFromLoader(loader cartridgeloader.Loader, numFram
|
|||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return thmb.renderChannel
|
||||
}
|
||||
|
||||
// CreateFromState will cause images to be returned from a running emulation initialised
|
||||
// with the specified cartridge loader. The emulation will run for a number of
|
||||
// frames before ending.
|
||||
// CreateFromState will cause images to be returned from a running emulation
|
||||
// initialised with the specified cartridge loader. The emulation will run for
|
||||
// a number of frames before ending.
|
||||
//
|
||||
// It returns the channel over which new frames will be sent.
|
||||
func (thmb *Thumbnailer) CreateFromState(state *rewind.State, numFrames int) chan *image.RGBA {
|
||||
// See comment for rewind.GetState() about how to use handle rewind.State.
|
||||
func (thmb *Thumbnailer) CreateFromState(state *rewind.State, numFrames int) {
|
||||
thmb.wait()
|
||||
|
||||
go func() {
|
||||
|
@ -181,10 +168,11 @@ func (thmb *Thumbnailer) CreateFromState(state *rewind.State, numFrames int) cha
|
|||
}()
|
||||
|
||||
rewind.Plumb(thmb.vcs, state, true)
|
||||
thmb.vcs.TIA.Audio.SetTracker(nil)
|
||||
thmb.vcs.RIOT.Ports.AttachEventRecorder(nil)
|
||||
thmb.vcs.RIOT.Ports.AttachPlayback(nil)
|
||||
thmb.vcs.RIOT.Ports.AttachPlugMonitor(nil)
|
||||
|
||||
// the state we've just plumbed into the thumbnailing emulation is from
|
||||
// a different emulation which potentially has some links to that
|
||||
// emulator still remaining
|
||||
thmb.vcs.DetatchEmulationExtras()
|
||||
|
||||
tgtFrame := thmb.vcs.TV.GetCoords().Frame + numFrames
|
||||
|
||||
|
@ -205,8 +193,6 @@ func (thmb *Thumbnailer) CreateFromState(state *rewind.State, numFrames int) cha
|
|||
return
|
||||
}
|
||||
}()
|
||||
|
||||
return thmb.renderChannel
|
||||
}
|
||||
|
||||
// Resize implements the television.PixelRenderer interface.
|
||||
|
@ -224,6 +210,10 @@ func (thmb *Thumbnailer) Resize(frameInfo television.FrameInfo) error {
|
|||
|
||||
// NewFrame implements the television.PixelRenderer interface.
|
||||
func (thmb *Thumbnailer) NewFrame(frameInfo television.FrameInfo) error {
|
||||
if !frameInfo.Stable {
|
||||
return nil
|
||||
}
|
||||
|
||||
thmb.frameInfo = frameInfo
|
||||
|
||||
img := *thmb.cropImg
|
||||
|
@ -231,7 +221,7 @@ func (thmb *Thumbnailer) NewFrame(frameInfo television.FrameInfo) error {
|
|||
copy(img.Pix, thmb.cropImg.Pix)
|
||||
|
||||
select {
|
||||
case thmb.renderChannel <- &img:
|
||||
case thmb.Render <- &img:
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -270,6 +260,22 @@ func (thmb *Thumbnailer) SetPixels(sig []signal.SignalAttributes, last int) erro
|
|||
|
||||
// Reset implements the television.PixelRenderer interface.
|
||||
func (thmb *Thumbnailer) Reset() {
|
||||
// clear pixels. setting the alpha channel so we don't have to later (the
|
||||
// alpha channel never changes)
|
||||
for y := 0; y < thmb.img.Bounds().Size().Y; y++ {
|
||||
for x := 0; x < thmb.img.Bounds().Size().X; x++ {
|
||||
thmb.img.SetRGBA(x, y, color.RGBA{0, 0, 0, 255})
|
||||
}
|
||||
}
|
||||
|
||||
img := *thmb.cropImg
|
||||
img.Pix = make([]uint8, len(thmb.cropImg.Pix))
|
||||
copy(img.Pix, thmb.cropImg.Pix)
|
||||
|
||||
select {
|
||||
case thmb.Render <- &img:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// EndRendering implements the television.PixelRenderer interface.
|
||||
|
|
Loading…
Reference in a new issue