From 8ad481e1329351e1bfb148773b0f7997a26d980d Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 13 Nov 2019 11:09:57 +0000 Subject: [PATCH] o debugger / playmode - removed references to SdlPlay and SdlDebug - constructors for debugger and playmode now expect instances of GUI and Television, rather than creating them - this should help future porting efforts o peripherals - renamed Events to Actions - to avoid confusion with Events in the GUI package o television - renamed StellaTelevision to television; a better name because it serves as a reference implementation and is the only television implementation currently needed. - originally, PixelRenderers were implemented as Television that embedded StellaTelevision; it made sense to use a more unique name - note that we're still keeping and using the Television interface - reworked specifications file --- debugger/commands.go | 40 +- debugger/debugger.go | 31 +- debugger/events.go | 12 +- errors/categories.go | 2 +- errors/messages.go | 8 +- gopher2600.go | 55 +- gui/events.go | 53 ++ gui/gui.go | 78 +-- gui/headless.go | 1 + gui/requests.go | 24 + gui/sdldebug/guiloop.go | 32 +- gui/sdldebug/overlay.go | 4 +- gui/sdldebug/pixels.go | 10 +- gui/sdldebug/requests.go | 64 +-- gui/sdldebug/sdldebug.go | 30 +- gui/sdlplay/sdlplay.go | 34 +- .../peripherals/{events.go => actions.go} | 14 +- hardware/peripherals/controller.go | 2 +- hardware/peripherals/panel.go | 4 +- hardware/peripherals/peripheral.go | 2 +- hardware/peripherals/ports.go | 6 +- hardware/peripherals/transcriber.go | 2 +- hardware/tia/video/ball.go | 14 +- hardware/tia/video/missile.go | 14 +- hardware/tia/video/player.go | 22 +- performance/check.go | 32 +- performance/fps.go | 4 +- playmode/play.go | 35 +- recorder/playback.go | 32 +- recorder/recorder.go | 6 +- regression/frame.go | 14 +- regression/playback.go | 7 +- screendigest/digest.go | 30 +- television/protocol.go | 155 ++++++ television/specifications.go | 98 ++-- television/stella.go | 350 ------------- television/television.go | 485 ++++++++++++------ test/gopher2600_test.go | 12 +- web2600/src/canvas.go | 32 +- web2600/src/web2600.go | 4 +- 40 files changed, 917 insertions(+), 937 deletions(-) create mode 100644 gui/events.go create mode 100644 gui/headless.go create mode 100644 gui/requests.go rename hardware/peripherals/{events.go => actions.go} (75%) create mode 100644 television/protocol.go delete mode 100644 television/stella.go diff --git a/debugger/commands.go b/debugger/commands.go index 6f2e4b96..dea1102b 100644 --- a/debugger/commands.go +++ b/debugger/commands.go @@ -618,13 +618,13 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) option, _ := tokens.Get() switch strings.ToUpper(option) { case "OFF": - err := dbg.gui.SetFeature(gui.ReqSetOverlay, false) + err := dbg.scr.SetFeature(gui.ReqSetOverlay, false) if err != nil { dbg.print(console.StyleError, err.Error()) } dbg.relfectMonitor.Activate(false) case "ON": - err := dbg.gui.SetFeature(gui.ReqSetOverlay, true) + err := dbg.scr.SetFeature(gui.ReqSetOverlay, true) if err != nil { dbg.print(console.StyleError, err.Error()) } @@ -647,14 +647,14 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) if err != nil { return doNothing, err } - err = dbg.gui.Reset() + err = dbg.tv.Reset() if err != nil { return doNothing, err } dbg.print(console.StyleFeedback, "machine reset") case cmdRun: - if !dbg.gui.IsVisible() && dbg.commandOnStep == "" { + if !dbg.scr.IsVisible() && dbg.commandOnStep == "" { dbg.print(console.StyleEmulatorInfo, "running with no display or terminal output") } dbg.runUntilHalt = true @@ -918,12 +918,12 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) option = strings.ToUpper(option) switch option { case "SPEC": - dbg.print(console.StyleInstrument, dbg.gui.GetSpec().ID) + dbg.print(console.StyleInstrument, dbg.tv.GetSpec().ID) default: // already caught by command line ValidateTokens() } } else { - dbg.printInstrument(dbg.gui) + dbg.printInstrument(dbg.tv) } case cmdPanel: @@ -1022,12 +1022,12 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) action = strings.ToUpper(action) switch action { case "ON": - err = dbg.gui.SetFeature(gui.ReqSetVisibility, true) + err = dbg.scr.SetFeature(gui.ReqSetVisibility, true) if err != nil { return doNothing, err } case "OFF": - err = dbg.gui.SetFeature(gui.ReqSetVisibility, false) + err = dbg.scr.SetFeature(gui.ReqSetVisibility, false) if err != nil { return doNothing, err } @@ -1036,17 +1036,17 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) action = strings.ToUpper(action) switch action { case "OFF": - err = dbg.gui.SetFeature(gui.ReqSetMasking, false) + err = dbg.scr.SetFeature(gui.ReqSetMasking, false) if err != nil { return doNothing, err } case "ON": - err = dbg.gui.SetFeature(gui.ReqSetMasking, true) + err = dbg.scr.SetFeature(gui.ReqSetMasking, true) if err != nil { return doNothing, err } default: - err = dbg.gui.SetFeature(gui.ReqToggleMasking) + err = dbg.scr.SetFeature(gui.ReqToggleMasking) if err != nil { return doNothing, err } @@ -1062,24 +1062,24 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) return doNothing, errors.New(errors.CommandError, fmt.Sprintf("%s %s value not valid (%s)", command, action, scl)) } - err = dbg.gui.SetFeature(gui.ReqSetScale, float32(scale)) + err = dbg.scr.SetFeature(gui.ReqSetScale, float32(scale)) return doNothing, err case "ALT": action, _ := tokens.Get() action = strings.ToUpper(action) switch action { case "OFF": - err = dbg.gui.SetFeature(gui.ReqSetAltColors, false) + err = dbg.scr.SetFeature(gui.ReqSetAltColors, false) if err != nil { return doNothing, err } case "ON": - err = dbg.gui.SetFeature(gui.ReqSetAltColors, true) + err = dbg.scr.SetFeature(gui.ReqSetAltColors, true) if err != nil { return doNothing, err } default: - err = dbg.gui.SetFeature(gui.ReqToggleAltColors) + err = dbg.scr.SetFeature(gui.ReqToggleAltColors) if err != nil { return doNothing, err } @@ -1093,23 +1093,23 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) action = strings.ToUpper(action) switch action { case "OFF": - err = dbg.gui.SetFeature(gui.ReqSetOverlay, false) + err = dbg.scr.SetFeature(gui.ReqSetOverlay, false) if err != nil { return doNothing, err } case "ON": - err = dbg.gui.SetFeature(gui.ReqSetOverlay, true) + err = dbg.scr.SetFeature(gui.ReqSetOverlay, true) if err != nil { return doNothing, err } default: - err = dbg.gui.SetFeature(gui.ReqToggleOverlay) + err = dbg.scr.SetFeature(gui.ReqToggleOverlay) if err != nil { return doNothing, err } } default: - err = dbg.gui.SetFeature(gui.ReqToggleVisibility) + err = dbg.scr.SetFeature(gui.ReqToggleVisibility) if err != nil { return doNothing, err } @@ -1121,7 +1121,7 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool) stick, _ := tokens.Get() action, _ := tokens.Get() - var event peripherals.Event + var event peripherals.Action switch strings.ToUpper(action) { case "UP": event = peripherals.Up diff --git a/debugger/debugger.go b/debugger/debugger.go index 1ee83662..e861c7f9 100644 --- a/debugger/debugger.go +++ b/debugger/debugger.go @@ -9,7 +9,6 @@ import ( "gopher2600/disassembly" "gopher2600/errors" "gopher2600/gui" - "gopher2600/gui/sdldebug" "gopher2600/hardware" "gopher2600/hardware/cpu/definitions" "gopher2600/screendigest" @@ -31,8 +30,9 @@ type Debugger struct { disasm *disassembly.Disassembly // gui/tv + tv television.Television + scr gui.GUI digest *screendigest.SHA1 - gui gui.GUI // whether the debugger is to continue with the debugging loop // set to false only when debugger is to finish @@ -122,29 +122,18 @@ type Debugger struct { // NewDebugger creates and initialises everything required for a new debugging // session. Use the Start() method to actually begin the session. -func NewDebugger(tvType string) (*Debugger, error) { +func NewDebugger(tv television.Television, scr gui.GUI) (*Debugger, error) { var err error - dbg := new(Debugger) + dbg := &Debugger{tv: tv, scr: scr} - // prepare gui/tv - btv, err := television.NewStellaTelevision(tvType) - if err != nil { - return nil, errors.New(errors.DebuggerError, err) - } - - dbg.digest, err = screendigest.NewSHA1(tvType, btv) - if err != nil { - return nil, errors.New(errors.DebuggerError, err) - } - - dbg.gui, err = sdldebug.NewSdlDebug(tvType, 2.0, btv) + dbg.digest, err = screendigest.NewSHA1(dbg.tv) if err != nil { return nil, errors.New(errors.DebuggerError, err) } // create a new VCS instance - dbg.vcs, err = hardware.NewVCS(dbg.gui) + dbg.vcs, err = hardware.NewVCS(dbg.tv) if err != nil { return nil, errors.New(errors.DebuggerError, err) } @@ -157,7 +146,7 @@ func NewDebugger(tvType string) (*Debugger, error) { dbg.dbgmem = &memoryDebug{mem: dbg.vcs.Mem, symtable: &dbg.disasm.Symtable} // set up reflection monitor - dbg.relfectMonitor = reflection.NewMonitor(dbg.vcs, dbg.gui) + dbg.relfectMonitor = reflection.NewMonitor(dbg.vcs, dbg.scr) dbg.relfectMonitor.Activate(true) // set up breakpoints/traps @@ -183,7 +172,7 @@ func NewDebugger(tvType string) (*Debugger, error) { signal.Notify(dbg.intChan, os.Interrupt) // connect debugger to gui - dbg.gui.SetEventChannel(dbg.guiChan) + dbg.scr.SetEventChannel(dbg.guiChan) return dbg, nil } @@ -386,7 +375,7 @@ func (dbg *Debugger) inputLoop(inputter console.UserInput, videoCycle bool) erro // enter halt state if dbg.inputloopHalt { // pause tv when emulation has halted - err = dbg.gui.SetFeature(gui.ReqSetPause, true) + err = dbg.scr.SetFeature(gui.ReqSetPause, true) if err != nil { return err } @@ -477,7 +466,7 @@ func (dbg *Debugger) inputLoop(inputter console.UserInput, videoCycle bool) erro // make sure tv is unpaused if emulation is about to resume if dbg.inputloopNext { - err = dbg.gui.SetFeature(gui.ReqSetPause, false) + err = dbg.scr.SetFeature(gui.ReqSetPause, false) if err != nil { return err } diff --git a/debugger/events.go b/debugger/events.go index d4663f8f..3a6059c9 100644 --- a/debugger/events.go +++ b/debugger/events.go @@ -17,7 +17,7 @@ func (dbg *Debugger) guiEventHandler(event gui.Event) error { data := event.Data.(gui.EventDataKeyboard) // check playmode key presses first - err = playmode.KeyboardEventHandler(data, dbg.gui, dbg.vcs) + err = playmode.KeyboardEventHandler(data, dbg.scr, dbg.vcs) if err != nil { break // switch event.ID } @@ -26,11 +26,11 @@ func (dbg *Debugger) guiEventHandler(event gui.Event) error { switch data.Key { case "`": // back-tick: toggle masking - err = dbg.gui.SetFeature(gui.ReqToggleMasking) + err = dbg.scr.SetFeature(gui.ReqToggleMasking) case "1": // toggle debugging colours - err = dbg.gui.SetFeature(gui.ReqToggleAltColors) + err = dbg.scr.SetFeature(gui.ReqToggleAltColors) case "2": // toggle overlay @@ -39,16 +39,16 @@ func (dbg *Debugger) guiEventHandler(event gui.Event) error { // return errors.New(errors.ReflectionNotRunning) // } - err = dbg.gui.SetFeature(gui.ReqToggleOverlay) + err = dbg.scr.SetFeature(gui.ReqToggleOverlay) case "=": fallthrough // equal sign is the same as plus, for convenience case "+": // increase scaling - err = dbg.gui.SetFeature(gui.ReqIncScale) + err = dbg.scr.SetFeature(gui.ReqIncScale) case "-": // decrease window scanling - err = dbg.gui.SetFeature(gui.ReqDecScale) + err = dbg.scr.SetFeature(gui.ReqDecScale) } } diff --git a/errors/categories.go b/errors/categories.go index 22e2630b..956d9615 100644 --- a/errors/categories.go +++ b/errors/categories.go @@ -102,7 +102,7 @@ const ( // tv UnknownTVRequest - StellaTelevision + Television // screen digest ScreenDigest diff --git a/errors/messages.go b/errors/messages.go index 2a172154..212bc3d0 100644 --- a/errors/messages.go +++ b/errors/messages.go @@ -87,12 +87,12 @@ var messages = map[Errno]string{ PeriphHardwareUnavailable: "peripheral error: controller hardware unavailable (%s)", UnknownPeriphEvent: "peripheral error: %s: unsupported event (%v)", - // tv - UnknownTVRequest: "tv error: unsupported request (%v)", - StellaTelevision: "tv error: StellaTV: %s", + // television + UnknownTVRequest: "television error: unsupported request (%v)", + Television: "television error: %s", // screen digest - ScreenDigest: "tv error: DigestTV: %s", + ScreenDigest: "television error: screendigest: %s", // gui UnknownGUIRequest: "gui error: unsupported request (%v)", diff --git a/gopher2600.go b/gopher2600.go index 15a36893..25132845 100644 --- a/gopher2600.go +++ b/gopher2600.go @@ -8,12 +8,16 @@ import ( "gopher2600/debugger/colorterm" "gopher2600/debugger/console" "gopher2600/disassembly" + "gopher2600/gui" + "gopher2600/gui/sdldebug" + "gopher2600/gui/sdlplay" "gopher2600/magicflags" "gopher2600/paths" "gopher2600/performance" "gopher2600/playmode" "gopher2600/recorder" "gopher2600/regression" + "gopher2600/television" "io" "math/rand" "os" @@ -96,7 +100,19 @@ func play(mf *magicflags.MagicFlags) bool { Format: *cartFormat, } - err := playmode.Play(*tvType, float32(*scaling), *stable, *record, cartload) + tv, err := television.NewTelevision(*tvType) + if err != nil { + fmt.Printf("* %s\n", err) + return false + } + + scr, err := sdlplay.NewSdlPlay(tv, float32(*scaling)) + if err != nil { + fmt.Printf("* %s\n", err) + return false + } + + err = playmode.Play(tv, scr, *stable, *record, cartload) if err != nil { fmt.Printf("* %s\n", err) return false @@ -123,7 +139,19 @@ func debug(mf *magicflags.MagicFlags) bool { return false } - dbg, err := debugger.NewDebugger(*tvType) + tv, err := television.NewTelevision(*tvType) + if err != nil { + fmt.Printf("* %s\n", err) + return false + } + + scr, err := sdldebug.NewSdlDebug(tv, 2.0) + if err != nil { + fmt.Printf("* %s\n", err) + return false + } + + dbg, err := debugger.NewDebugger(tv, scr) if err != nil { fmt.Printf("* %s\n", err) return false @@ -242,7 +270,28 @@ func perform(mf *magicflags.MagicFlags) bool { Filename: mf.SubModeFlags.Arg(0), Format: *cartFormat, } - err := performance.Check(os.Stdout, *profile, *display, *tvType, float32(*scaling), *runTime, cartload) + + tv, err := television.NewTelevision(*tvType) + if err != nil { + fmt.Printf("* %s\n", err) + return false + } + + if *display { + scr, err := sdlplay.NewSdlPlay(tv, float32(*scaling)) + if err != nil { + fmt.Printf("* %s\n", err) + return false + } + + err = scr.(gui.GUI).SetFeature(gui.ReqSetVisibility, true) + if err != nil { + fmt.Printf("* %s\n", err) + return false + } + } + + err = performance.Check(os.Stdout, *profile, tv, *runTime, cartload) if err != nil { fmt.Printf("* %s\n", err) return false diff --git a/gui/events.go b/gui/events.go new file mode 100644 index 00000000..e1fc2e7d --- /dev/null +++ b/gui/events.go @@ -0,0 +1,53 @@ +package gui + +// Events are the things that happen in the gui, as a result of user interaction, +// and sent over a registered event channel. +// +// Do not confuse this with the peripheral Action type. + +// EventID idintifies the type of event taking place +type EventID int + +// list of valid events +const ( + EventWindowClose EventID = iota + EventKeyboard + EventMouseLeft + EventMouseRight +) + +// KeyMod identifies +type KeyMod int + +// list of valud key modifiers +const ( + KeyModNone KeyMod = iota + KeyModShift + KeyModCtrl + KeyModAlt +) + +// EventData represents the data that is associated with an event +type EventData interface{} + +// Event is the structure that is passed over the event channel +type Event struct { + ID EventID + Data EventData +} + +// EventDataKeyboard is the data that accompanies EvenKeyboard events +type EventDataKeyboard struct { + Key string + Down bool + Mod KeyMod +} + +// EventDataMouse is the data that accompanies EventMouse events +type EventDataMouse struct { + Down bool + X int + Y int + HorizPos int + Scanline int +} diff --git a/gui/gui.go b/gui/gui.go index 4bf14110..60af7a32 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -1,9 +1,5 @@ package gui -import ( - "gopher2600/television" -) - // GUI defines the operations that can be performed on visual user interfaces. // // Currently, GUI implementations expect also to be an instance of @@ -12,9 +8,7 @@ import ( // Renderer and AudioMixer interfaces from the television packages but this is // not mandated by the GUI interface. type GUI interface { - television.Television - - // All GUIs should implement a MetaPixelRenderer even if only as a stub + // All GUIs should implement a MetaPixelRenderer even if only a stub MetaPixelRenderer // returns true if GUI is currently visible. false if not @@ -29,73 +23,3 @@ type GUI interface { // purpose. SetEventChannel(chan (Event)) } - -// FeatureReq is used to request the setting of a gui attribute -// eg. toggling the overlay -type FeatureReq int - -// list of valid feature requests. argument must be of the type specified or -// else the interface{} type conversion will fail and the application will -// probably crash -const ( - ReqSetVisibility FeatureReq = iota // bool, optional bool (update on show) default true - ReqToggleVisibility // optional bool (update on show) default true - ReqSetVisibilityStable // none - ReqSetPause // bool - ReqSetMasking // bool - ReqToggleMasking // none - ReqSetAltColors // bool - ReqToggleAltColors // none - ReqSetOverlay // bool - ReqToggleOverlay // none - ReqSetScale // float - ReqIncScale // none - ReqDecScale // none -) - -// EventID idintifies the type of event taking place -type EventID int - -// list of valid events -const ( - EventWindowClose EventID = iota - EventKeyboard - EventMouseLeft - EventMouseRight -) - -// KeyMod identifies -type KeyMod int - -// list of valud key modifiers -const ( - KeyModNone KeyMod = iota - KeyModShift - KeyModCtrl - KeyModAlt -) - -// EventData represents the data that is associated with an event -type EventData interface{} - -// Event is the structure that is passed over the event channel -type Event struct { - ID EventID - Data EventData -} - -// EventDataKeyboard is the data that accompanies EvenKeyboard events -type EventDataKeyboard struct { - Key string - Down bool - Mod KeyMod -} - -// EventDataMouse is the data that accompanies EventMouse events -type EventDataMouse struct { - Down bool - X int - Y int - HorizPos int - Scanline int -} diff --git a/gui/headless.go b/gui/headless.go new file mode 100644 index 00000000..194757a7 --- /dev/null +++ b/gui/headless.go @@ -0,0 +1 @@ +package gui diff --git a/gui/requests.go b/gui/requests.go new file mode 100644 index 00000000..9be0c94c --- /dev/null +++ b/gui/requests.go @@ -0,0 +1,24 @@ +package gui + +// FeatureReq is used to request the setting of a gui attribute +// eg. toggling the overlay +type FeatureReq int + +// list of valid feature requests. argument must be of the type specified or +// else the interface{} type conversion will fail and the application will +// probably crash +const ( + ReqSetVisibility FeatureReq = iota // bool, optional bool (update on show) default true + ReqToggleVisibility // optional bool (update on show) default true + ReqSetVisibilityStable // none + ReqSetPause // bool + ReqSetMasking // bool + ReqToggleMasking // none + ReqSetAltColors // bool + ReqToggleAltColors // none + ReqSetOverlay // bool + ReqToggleOverlay // none + ReqSetScale // float + ReqIncScale // none + ReqDecScale // none +) diff --git a/gui/sdldebug/guiloop.go b/gui/sdldebug/guiloop.go index a156e961..43adef7f 100644 --- a/gui/sdldebug/guiloop.go +++ b/gui/sdldebug/guiloop.go @@ -8,15 +8,15 @@ import ( ) // guiLoop listens for SDL events and is run concurrently -func (pxtv *SdlDebug) guiLoop() { +func (scr *SdlDebug) guiLoop() { for { sdlEvent := sdl.WaitEvent() switch sdlEvent := sdlEvent.(type) { // close window case *sdl.QuitEvent: - pxtv.SetFeature(gui.ReqSetVisibility, false) - pxtv.eventChannel <- gui.Event{ID: gui.EventWindowClose} + scr.SetFeature(gui.ReqSetVisibility, false) + scr.eventChannel <- gui.Event{ID: gui.EventWindowClose} case *sdl.KeyboardEvent: mod := gui.KeyModNone @@ -35,7 +35,7 @@ func (pxtv *SdlDebug) guiLoop() { switch sdlEvent.Type { case sdl.KEYDOWN: if sdlEvent.Repeat == 0 { - pxtv.eventChannel <- gui.Event{ + scr.eventChannel <- gui.Event{ ID: gui.EventKeyboard, Data: gui.EventDataKeyboard{ Key: sdl.GetKeyName(sdlEvent.Keysym.Sym), @@ -44,7 +44,7 @@ func (pxtv *SdlDebug) guiLoop() { } case sdl.KEYUP: if sdlEvent.Repeat == 0 { - pxtv.eventChannel <- gui.Event{ + scr.eventChannel <- gui.Event{ ID: gui.EventKeyboard, Data: gui.EventDataKeyboard{ Key: sdl.GetKeyName(sdlEvent.Keysym.Sym), @@ -54,13 +54,13 @@ func (pxtv *SdlDebug) guiLoop() { } case *sdl.MouseButtonEvent: - hp, sl := pxtv.convertMouseCoords(sdlEvent) + hp, sl := scr.convertMouseCoords(sdlEvent) switch sdlEvent.Type { case sdl.MOUSEBUTTONDOWN: switch sdlEvent.Button { case sdl.BUTTON_LEFT: - pxtv.eventChannel <- gui.Event{ + scr.eventChannel <- gui.Event{ ID: gui.EventMouseLeft, Data: gui.EventDataMouse{ Down: true, @@ -70,7 +70,7 @@ func (pxtv *SdlDebug) guiLoop() { Scanline: sl}} case sdl.BUTTON_RIGHT: - pxtv.eventChannel <- gui.Event{ + scr.eventChannel <- gui.Event{ ID: gui.EventMouseRight, Data: gui.EventDataMouse{ Down: true, @@ -84,7 +84,7 @@ func (pxtv *SdlDebug) guiLoop() { switch sdlEvent.Button { case sdl.BUTTON_LEFT: - pxtv.eventChannel <- gui.Event{ + scr.eventChannel <- gui.Event{ ID: gui.EventMouseLeft, Data: gui.EventDataMouse{ Down: false, @@ -94,7 +94,7 @@ func (pxtv *SdlDebug) guiLoop() { Scanline: sl}} case sdl.BUTTON_RIGHT: - pxtv.eventChannel <- gui.Event{ + scr.eventChannel <- gui.Event{ ID: gui.EventMouseRight, Data: gui.EventDataMouse{ Down: false, @@ -116,16 +116,16 @@ func (pxtv *SdlDebug) guiLoop() { } } -func (pxtv *SdlDebug) convertMouseCoords(sdlEvent *sdl.MouseButtonEvent) (int, int) { +func (scr *SdlDebug) convertMouseCoords(sdlEvent *sdl.MouseButtonEvent) (int, int) { var hp, sl int - sx, sy := pxtv.pxl.renderer.GetScale() + sx, sy := scr.pxl.renderer.GetScale() // convert X pixel value to horizpos equivalent // the opposite of pixelX() and also the scalining applied // by the SDL renderer - if pxtv.pxl.unmasked { - hp = int(float32(sdlEvent.X)/sx) - television.ClocksPerHblank + if scr.pxl.unmasked { + hp = int(float32(sdlEvent.X)/sx) - television.HorizClksHBlank } else { hp = int(float32(sdlEvent.X) / sx) } @@ -133,10 +133,10 @@ func (pxtv *SdlDebug) convertMouseCoords(sdlEvent *sdl.MouseButtonEvent) (int, i // convert Y pixel value to scanline equivalent // the opposite of pixelY() and also the scalining applied // by the SDL renderer - if pxtv.pxl.unmasked { + if scr.pxl.unmasked { sl = int(float32(sdlEvent.Y) / sy) } else { - sl = int(float32(sdlEvent.Y)/sy) + int(pxtv.pxl.playTop) + sl = int(float32(sdlEvent.Y)/sy) + int(scr.pxl.playTop) } return hp, sl diff --git a/gui/sdldebug/overlay.go b/gui/sdldebug/overlay.go index 2034113a..e71f3be6 100644 --- a/gui/sdldebug/overlay.go +++ b/gui/sdldebug/overlay.go @@ -109,6 +109,6 @@ func (ovl *metapixelOverlay) update(paused bool) error { } // SetMetaPixel recieves (and processes) additional emulator information from the emulator -func (pxtv *SdlDebug) SetMetaPixel(sig gui.MetaPixel) error { - return pxtv.pxl.metaPixels.setPixel(sig) +func (scr *SdlDebug) SetMetaPixel(sig gui.MetaPixel) error { + return scr.pxl.metaPixels.setPixel(sig) } diff --git a/gui/sdldebug/pixels.go b/gui/sdldebug/pixels.go index b9575ba8..6ef3ee2b 100644 --- a/gui/sdldebug/pixels.go +++ b/gui/sdldebug/pixels.go @@ -97,12 +97,12 @@ func (pxl *pixels) reset() error { func (pxl *pixels) resize(topScanline, numScanlines int) error { var err error - pxl.maxWidth = int32(television.ClocksPerScanline) + pxl.maxWidth = int32(television.HorizClksScanline) pxl.maxHeight = int32(pxl.scr.GetSpec().ScanlinesTotal) pxl.maxMask = &sdl.Rect{X: 0, Y: 0, W: pxl.maxWidth, H: pxl.maxHeight} pxl.playTop = int32(topScanline) - pxl.playWidth = int32(television.ClocksPerVisible) + pxl.playWidth = int32(television.HorizClksVisible) pxl.setPlayArea(int32(numScanlines), int32(topScanline)) // screen texture is used to draw the pixels onto the sdl window (by the @@ -143,7 +143,7 @@ func (pxl *pixels) resize(topScanline, numScanlines int) error { func (pxl *pixels) setPlayArea(scanlines int32, top int32) { pxl.playHeight = scanlines pxl.playDstMask = &sdl.Rect{X: 0, Y: 0, W: pxl.playWidth, H: pxl.playHeight} - pxl.playSrcMask = &sdl.Rect{X: int32(television.ClocksPerHblank), Y: top, W: pxl.playWidth, H: pxl.playHeight} + pxl.playSrcMask = &sdl.Rect{X: int32(television.HorizClksHBlank), Y: top, W: pxl.playWidth, H: pxl.playHeight} pxl.setMasking(pxl.unmasked) } @@ -273,7 +273,7 @@ func (pxl *pixels) update() error { if pxl.unmasked { pxl.renderer.SetDrawColor(100, 100, 100, 20) pxl.renderer.SetDrawBlendMode(sdl.BlendMode(sdl.BLENDMODE_BLEND)) - pxl.renderer.FillRect(&sdl.Rect{X: 0, Y: 0, W: int32(television.ClocksPerHblank), H: int32(pxl.scr.GetSpec().ScanlinesTotal)}) + pxl.renderer.FillRect(&sdl.Rect{X: 0, Y: 0, W: int32(television.HorizClksHBlank), H: int32(pxl.scr.GetSpec().ScanlinesTotal)}) } // show overlay @@ -293,7 +293,7 @@ func (pxl *pixels) update() error { // cursor is one step ahead of pixel -- move to new scanline if // necessary - if x >= television.ClocksPerScanline { + if x >= television.HorizClksScanline { x = 0 y++ } diff --git a/gui/sdldebug/requests.go b/gui/sdldebug/requests.go index 5400f48c..45969c7c 100644 --- a/gui/sdldebug/requests.go +++ b/gui/sdldebug/requests.go @@ -8,7 +8,7 @@ import ( ) // SetFeature is used to set a television attribute -func (pxtv *SdlDebug) SetFeature(request gui.FeatureReq, args ...interface{}) (returnedErr error) { +func (scr *SdlDebug) SetFeature(request gui.FeatureReq, args ...interface{}) (returnedErr error) { // lazy (but clear) handling of type assertion errors defer func() { if r := recover(); r != nil { @@ -22,72 +22,72 @@ func (pxtv *SdlDebug) SetFeature(request gui.FeatureReq, args ...interface{}) (r case gui.ReqSetVisibility: if args[0].(bool) { - pxtv.window.Show() + scr.window.Show() // update screen // -- default args[1] of true if not present if len(args) < 2 || args[1].(bool) { - pxtv.pxl.update() + scr.pxl.update() } } else { - pxtv.window.Hide() + scr.window.Hide() } case gui.ReqToggleVisibility: - if pxtv.window.GetFlags()&sdl.WINDOW_HIDDEN == sdl.WINDOW_HIDDEN { - pxtv.window.Show() + if scr.window.GetFlags()&sdl.WINDOW_HIDDEN == sdl.WINDOW_HIDDEN { + scr.window.Show() // update screen // -- default args[1] of true if not present if len(args) < 2 || args[1].(bool) { - pxtv.pxl.update() + scr.pxl.update() } } else { - pxtv.window.Hide() + scr.window.Hide() } case gui.ReqSetPause: - pxtv.paused = args[0].(bool) - pxtv.pxl.update() + scr.paused = args[0].(bool) + scr.pxl.update() case gui.ReqSetMasking: - pxtv.pxl.setMasking(args[0].(bool)) - pxtv.pxl.update() + scr.pxl.setMasking(args[0].(bool)) + scr.pxl.update() case gui.ReqToggleMasking: - pxtv.pxl.setMasking(!pxtv.pxl.unmasked) - pxtv.pxl.update() + scr.pxl.setMasking(!scr.pxl.unmasked) + scr.pxl.update() case gui.ReqSetAltColors: - pxtv.pxl.useAltPixels = args[0].(bool) - pxtv.pxl.update() + scr.pxl.useAltPixels = args[0].(bool) + scr.pxl.update() case gui.ReqToggleAltColors: - pxtv.pxl.useAltPixels = !pxtv.pxl.useAltPixels - pxtv.pxl.update() + scr.pxl.useAltPixels = !scr.pxl.useAltPixels + scr.pxl.update() case gui.ReqSetOverlay: - pxtv.pxl.useMetaPixels = args[0].(bool) - pxtv.pxl.update() + scr.pxl.useMetaPixels = args[0].(bool) + scr.pxl.update() case gui.ReqToggleOverlay: - pxtv.pxl.useMetaPixels = !pxtv.pxl.useMetaPixels - pxtv.pxl.update() + scr.pxl.useMetaPixels = !scr.pxl.useMetaPixels + scr.pxl.update() case gui.ReqSetScale: - pxtv.pxl.setScaling(args[0].(float32)) - pxtv.pxl.update() + scr.pxl.setScaling(args[0].(float32)) + scr.pxl.update() case gui.ReqIncScale: - if pxtv.pxl.pixelScaleY < 4.0 { - pxtv.pxl.setScaling(pxtv.pxl.pixelScaleY + 0.1) - pxtv.pxl.update() + if scr.pxl.pixelScaleY < 4.0 { + scr.pxl.setScaling(scr.pxl.pixelScaleY + 0.1) + scr.pxl.update() } case gui.ReqDecScale: - if pxtv.pxl.pixelScaleY > 0.5 { - pxtv.pxl.setScaling(pxtv.pxl.pixelScaleY - 0.1) - pxtv.pxl.update() + if scr.pxl.pixelScaleY > 0.5 { + scr.pxl.setScaling(scr.pxl.pixelScaleY - 0.1) + scr.pxl.update() } default: @@ -98,6 +98,6 @@ func (pxtv *SdlDebug) SetFeature(request gui.FeatureReq, args ...interface{}) (r } // SetEventChannel implements the GUI interface -func (pxtv *SdlDebug) SetEventChannel(eventChannel chan gui.Event) { - pxtv.eventChannel = eventChannel +func (scr *SdlDebug) SetEventChannel(eventChannel chan gui.Event) { + scr.eventChannel = eventChannel } diff --git a/gui/sdldebug/sdldebug.go b/gui/sdldebug/sdldebug.go index 07de46ad..0583c543 100644 --- a/gui/sdldebug/sdldebug.go +++ b/gui/sdldebug/sdldebug.go @@ -4,7 +4,6 @@ import ( "gopher2600/errors" "gopher2600/gui" "gopher2600/television" - "strings" "github.com/veandco/go-sdl2/sdl" ) @@ -27,33 +26,12 @@ type SdlDebug struct { paused bool } -// NewSdlDebug creates a new instance of PixelTV. For convenience, the -// television argument can be nil, in which case an instance of -// StellaTelevision will be created. -func NewSdlDebug(tvType string, scale float32, tv television.Television) (gui.GUI, error) { +// NewSdlDebug is the preferred method for creating a new instance of SdlDebug +func NewSdlDebug(tv television.Television, scale float32) (gui.GUI, error) { var err error // set up gui - scr := new(SdlDebug) - - // create or attach television implementation - if tv == nil { - scr.Television, err = television.NewStellaTelevision(tvType) - if err != nil { - return nil, errors.New(errors.SDL, err) - } - } else { - // check that the quoted tvType matches the specification of the - // supplied BasicTelevision instance. we don't really need this but - // becuase we're implying that tvType is required, even when an - // instance of BasicTelevision has been supplied, the caller may be - // expecting an error - tvType = strings.ToUpper(tvType) - if tvType != "AUTO" && tvType != tv.GetSpec().ID { - return nil, errors.New(errors.SDL, "trying to piggyback a tv of a different spec") - } - scr.Television = tv - } + scr := &SdlDebug{Television: tv} // set up sdl err = sdl.Init(sdl.INIT_EVERYTHING) @@ -74,7 +52,7 @@ func NewSdlDebug(tvType string, scale float32, tv television.Television) (gui.GU } // set attributes that depend on the television specification - err = scr.Resize(scr.GetSpec().ScanlineTop, scr.GetSpec().ScanlinesPerVisible) + err = scr.Resize(scr.GetSpec().ScanlineTop, scr.GetSpec().ScanlinesVisible) if err != nil { return nil, errors.New(errors.SDL, err) } diff --git a/gui/sdlplay/sdlplay.go b/gui/sdlplay/sdlplay.go index e61de2b4..c9193a03 100644 --- a/gui/sdlplay/sdlplay.go +++ b/gui/sdlplay/sdlplay.go @@ -5,7 +5,6 @@ import ( "gopher2600/gui" "gopher2600/performance/limiter" "gopher2600/television" - "strings" "github.com/veandco/go-sdl2/sdl" ) @@ -54,34 +53,13 @@ type SdlPlay struct { showOnNextStable bool } -// NewSdlPlay creates a new instance of SdlPlay. For convenience, the -// television argument can be nil, in which case an instance of -// StellaTelevision will be created. -func NewSdlPlay(tvType string, scale float32, tv television.Television) (gui.GUI, error) { +// NewSdlPlay is the preferred method of initialisation for SdlPlay +func NewSdlPlay(tv television.Television, scale float32) (gui.GUI, error) { // set up gui - scr := &SdlPlay{} + scr := &SdlPlay{Television: tv} var err error - // create or attach television implementation - if tv == nil { - scr.Television, err = television.NewStellaTelevision(tvType) - if err != nil { - return nil, errors.New(errors.SDL, err) - } - } else { - // check that the quoted tvType matches the specification of the - // supplied BasicTelevision instance. we don't really need this but - // becuase we're implying that tvType is required, even when an - // instance of BasicTelevision has been supplied, the caller may be - // expecting an error - tvType = strings.ToUpper(tvType) - if tvType != "AUTO" && tvType != tv.GetSpec().ID { - return nil, errors.New(errors.SDL, "trying to piggyback a tv of a different spec") - } - scr.Television = tv - } - // set up sdl err = sdl.Init(sdl.INIT_EVERYTHING) if err != nil { @@ -117,7 +95,7 @@ func NewSdlPlay(tvType string, scale float32, tv television.Television) (gui.GUI scr.AddAudioMixer(scr) // change tv spec after window creation (so we can set the window size) - err = scr.Resize(scr.GetSpec().ScanlineTop, scr.GetSpec().ScanlinesPerVisible) + err = scr.Resize(scr.GetSpec().ScanlineTop, scr.GetSpec().ScanlinesVisible) if err != nil { return nil, errors.New(errors.SDL, err) } @@ -146,7 +124,7 @@ func NewSdlPlay(tvType string, scale float32, tv television.Television) (gui.GUI func (scr *SdlPlay) Resize(topScanline, numScanlines int) error { var err error - scr.horizPixels = television.ClocksPerVisible + scr.horizPixels = television.HorizClksVisible scr.scanlines = int32(numScanlines) scr.topScanline = topScanline scr.pixels = make([]byte, scr.horizPixels*scr.scanlines*pixelDepth) @@ -236,7 +214,7 @@ func (scr *SdlPlay) SetPixel(x, y int, red, green, blue byte, vblank bool) error } // adjust pixels so we're only dealing with the visible range - x -= television.ClocksPerHblank + x -= television.HorizClksHBlank y -= scr.topScanline if x < 0 || y < 0 { diff --git a/hardware/peripherals/events.go b/hardware/peripherals/actions.go similarity index 75% rename from hardware/peripherals/events.go rename to hardware/peripherals/actions.go index b867c050..bdbc709b 100644 --- a/hardware/peripherals/events.go +++ b/hardware/peripherals/actions.go @@ -1,18 +1,18 @@ package peripherals -// Event represents the possible actions that can be performed with a -// controller -type Event int +// Action represents the possible actions that can be performed by the user +// when interacting with the console +type Action int -// list of defined events +// list of defined actions // // *** do not monkey with the ordering of these constants unless you know what // you're doing. existing playback scripts will probably break *** const ( - NoEvent Event = iota + NoAction Action = iota // the controller has been unplugged - Unplugged + Unplug // joystick Fire @@ -43,5 +43,5 @@ const ( // !!TODO: paddle and keyboard controllers - PanelPowerOff Event = 255 + PanelPowerOff Action = 255 ) diff --git a/hardware/peripherals/controller.go b/hardware/peripherals/controller.go index 2bbf320e..157b8cb1 100644 --- a/hardware/peripherals/controller.go +++ b/hardware/peripherals/controller.go @@ -6,5 +6,5 @@ package peripherals // Peripherals can also be controlled more directly by calling the Handle // function of that peripheral. type Controller interface { - GetInput(id PeriphID) (Event, error) + GetInput(id PeriphID) (Action, error) } diff --git a/hardware/peripherals/panel.go b/hardware/peripherals/panel.go index ad7fd44d..0d259331 100644 --- a/hardware/peripherals/panel.go +++ b/hardware/peripherals/panel.go @@ -100,11 +100,11 @@ func (pan *Panel) commit() { } // Handle interprets an event into the correct sequence of memory addressing -func (pan *Panel) Handle(event Event) error { +func (pan *Panel) Handle(event Action) error { switch event { // do nothing at all if event is a NoEvent - case NoEvent: + case NoAction: return nil case PanelSelectPress: diff --git a/hardware/peripherals/peripheral.go b/hardware/peripherals/peripheral.go index 5df85c8e..4cb9cd9d 100644 --- a/hardware/peripherals/peripheral.go +++ b/hardware/peripherals/peripheral.go @@ -15,7 +15,7 @@ const ( type peripheral struct { id PeriphID - handle func(Event) error + handle func(Action) error controller Controller prevController Controller diff --git a/hardware/peripherals/ports.go b/hardware/peripherals/ports.go index 4dbe05ac..46e2a499 100644 --- a/hardware/peripherals/ports.go +++ b/hardware/peripherals/ports.go @@ -95,11 +95,11 @@ func newPlayer1(riot memory.PeriphBus, tia memory.PeriphBus, panel *Panel) *play } // Handle interprets an event into the correct sequence of memory addressing -func (pl *player) Handle(event Event) error { +func (pl *player) Handle(event Action) error { switch event { // do nothing at all if event is a NoEvent - case NoEvent: + case NoAction: return nil case Left: @@ -144,7 +144,7 @@ func (pl *player) Handle(event Event) error { case PanelResetRelease: return pl.panel.Handle(PanelResetRelease) - case Unplugged: + case Unplug: return errors.New(errors.PeriphUnplugged, pl.id) // return now if there is no event to process diff --git a/hardware/peripherals/transcriber.go b/hardware/peripherals/transcriber.go index 60f74f6b..37698822 100644 --- a/hardware/peripherals/transcriber.go +++ b/hardware/peripherals/transcriber.go @@ -12,5 +12,5 @@ package peripherals // be used as the source for controller input (by implementing the Controller // interface). type Transcriber interface { - Transcribe(id PeriphID, event Event) error + Transcribe(id PeriphID, event Action) error } diff --git a/hardware/tia/video/ball.go b/hardware/tia/video/ball.go index 3bce4a49..46f22a41 100644 --- a/hardware/tia/video/ball.go +++ b/hardware/tia/video/ball.go @@ -132,10 +132,10 @@ func (bs *ballSprite) rsync(adjustment int) { bs.resetPixel -= adjustment bs.hmovedPixel -= adjustment if bs.resetPixel < 0 { - bs.resetPixel += television.ClocksPerVisible + bs.resetPixel += television.HorizClksVisible } if bs.hmovedPixel < 0 { - bs.hmovedPixel += television.ClocksPerVisible + bs.hmovedPixel += television.HorizClksVisible } } @@ -162,7 +162,7 @@ func (bs *ballSprite) tick(visible, isHmove bool, hmoveCt uint8) { // adjust for screen boundary if bs.hmovedPixel < 0 { - bs.hmovedPixel += television.ClocksPerVisible + bs.hmovedPixel += television.HorizClksVisible } } @@ -199,8 +199,8 @@ func (bs *ballSprite) prepareForHMOVE() { bs.hmovedPixel += 8 // adjust for screen boundary - if bs.hmovedPixel > television.ClocksPerVisible { - bs.hmovedPixel -= television.ClocksPerVisible + if bs.hmovedPixel > television.HorizClksVisible { + bs.hmovedPixel -= television.HorizClksVisible } } } @@ -258,8 +258,8 @@ func (bs *ballSprite) _futureResetPosition() { bs.resetPixel++ // adjust resetPixel for screen boundaries - if bs.resetPixel > television.ClocksPerVisible { - bs.resetPixel -= television.ClocksPerVisible + if bs.resetPixel > television.HorizClksVisible { + bs.resetPixel -= television.HorizClksVisible } // by definition the current pixel is the same as the reset pixel at diff --git a/hardware/tia/video/missile.go b/hardware/tia/video/missile.go index d91e9a44..e5e122ac 100644 --- a/hardware/tia/video/missile.go +++ b/hardware/tia/video/missile.go @@ -157,10 +157,10 @@ func (ms *missileSprite) rsync(adjustment int) { ms.resetPixel -= adjustment ms.hmovedPixel -= adjustment if ms.resetPixel < 0 { - ms.resetPixel += television.ClocksPerVisible + ms.resetPixel += television.HorizClksVisible } if ms.hmovedPixel < 0 { - ms.hmovedPixel += television.ClocksPerVisible + ms.hmovedPixel += television.HorizClksVisible } } @@ -204,7 +204,7 @@ func (ms *missileSprite) tick(visible, isHmove bool, hmoveCt uint8) { // adjust for screen boundary if ms.hmovedPixel < 0 { - ms.hmovedPixel += television.ClocksPerVisible + ms.hmovedPixel += television.HorizClksVisible } } @@ -274,8 +274,8 @@ func (ms *missileSprite) prepareForHMOVE() { ms.hmovedPixel += 8 // adjust for screen boundary - if ms.hmovedPixel > television.ClocksPerVisible { - ms.hmovedPixel -= television.ClocksPerVisible + if ms.hmovedPixel > television.HorizClksVisible { + ms.hmovedPixel -= television.HorizClksVisible } } } @@ -331,8 +331,8 @@ func (ms *missileSprite) _futureResetPosition() { ms.resetPixel++ // adjust resetPixel for screen boundaries - if ms.resetPixel > television.ClocksPerVisible { - ms.resetPixel -= television.ClocksPerVisible + if ms.resetPixel > television.HorizClksVisible { + ms.resetPixel -= television.HorizClksVisible } // by definition the current pixel is the same as the reset pixel at diff --git a/hardware/tia/video/player.go b/hardware/tia/video/player.go index 138421a5..030a5c60 100644 --- a/hardware/tia/video/player.go +++ b/hardware/tia/video/player.go @@ -242,10 +242,10 @@ func (ps *playerSprite) rsync(adjustment int) { ps.resetPixel -= adjustment ps.hmovedPixel -= adjustment if ps.resetPixel < 0 { - ps.resetPixel += television.ClocksPerVisible + ps.resetPixel += television.HorizClksVisible } if ps.hmovedPixel < 0 { - ps.hmovedPixel += television.ClocksPerVisible + ps.hmovedPixel += television.HorizClksVisible } } @@ -269,7 +269,7 @@ func (ps *playerSprite) tick(visible, isHmove bool, hmoveCt uint8) { // adjust for screen boundary if ps.hmovedPixel < 0 { - ps.hmovedPixel += television.ClocksPerVisible + ps.hmovedPixel += television.HorizClksVisible } } @@ -381,8 +381,8 @@ func (ps *playerSprite) prepareForHMOVE() { ps.hmovedPixel += 8 // adjust for screen boundary - if ps.hmovedPixel > television.ClocksPerVisible { - ps.hmovedPixel -= television.ClocksPerVisible + if ps.hmovedPixel > television.HorizClksVisible { + ps.hmovedPixel -= television.HorizClksVisible } } } @@ -459,8 +459,8 @@ func (ps *playerSprite) _futureResetPosition() { } // adjust resetPixel for screen boundaries - if ps.resetPixel > television.ClocksPerVisible { - ps.resetPixel -= television.ClocksPerVisible + if ps.resetPixel > television.HorizClksVisible { + ps.resetPixel -= television.HorizClksVisible } // by definition the current pixel is the same as the reset pixel at @@ -641,11 +641,11 @@ func (ps *playerSprite) _futureSetNusiz(v interface{}) { } // adjust reset pixel for screen boundaries - if ps.resetPixel > television.ClocksPerVisible { - ps.resetPixel -= television.ClocksPerVisible + if ps.resetPixel > television.HorizClksVisible { + ps.resetPixel -= television.HorizClksVisible } - if ps.hmovedPixel > television.ClocksPerVisible { - ps.hmovedPixel -= television.ClocksPerVisible + if ps.hmovedPixel > television.HorizClksVisible { + ps.hmovedPixel -= television.HorizClksVisible } } diff --git a/performance/check.go b/performance/check.go index 566e7dce..b605193f 100644 --- a/performance/check.go +++ b/performance/check.go @@ -4,8 +4,6 @@ import ( "fmt" "gopher2600/cartridgeloader" "gopher2600/errors" - "gopher2600/gui" - "gopher2600/gui/sdlplay" "gopher2600/hardware" "gopher2600/setup" "gopher2600/television" @@ -14,31 +12,11 @@ import ( ) // Check is a very rough and ready calculation of the emulator's performance -func Check(output io.Writer, profile bool, display bool, tvType string, scaling float32, runTime string, cartload cartridgeloader.Loader) error { - var ftv television.Television +func Check(output io.Writer, profile bool, tv television.Television, runTime string, cartload cartridgeloader.Loader) error { var err error - // create the "correct" type of TV depending on whether the display flag is - // set or not - if display { - ftv, err = sdlplay.NewSdlPlay(tvType, scaling, nil) - if err != nil { - return errors.New(errors.PerformanceError, err) - } - - err = ftv.(gui.GUI).SetFeature(gui.ReqSetVisibility, true) - if err != nil { - return errors.New(errors.PerformanceError, err) - } - } else { - ftv, err = television.NewStellaTelevision(tvType) - if err != nil { - return errors.New(errors.PerformanceError, err) - } - } - // create vcs using the tv created above - vcs, err := hardware.NewVCS(ftv) + vcs, err := hardware.NewVCS(tv) if err != nil { return errors.New(errors.PerformanceError, err) } @@ -56,7 +34,7 @@ func Check(output io.Writer, profile bool, display bool, tvType string, scaling } // get starting frame number (should be 0) - startFrame, err := ftv.GetState(television.ReqFramenum) + startFrame, err := tv.GetState(television.ReqFramenum) if err != nil { return errors.New(errors.PerformanceError, err) } @@ -70,7 +48,7 @@ func Check(output io.Writer, profile bool, display bool, tvType string, scaling // then restart timer for the specified duration go func() { time.AfterFunc(2*time.Second, func() { - startFrame, _ = ftv.GetState(television.ReqFramenum) + startFrame, _ = tv.GetState(television.ReqFramenum) time.AfterFunc(duration, func() { timesUp <- true }) @@ -109,7 +87,7 @@ func Check(output io.Writer, profile bool, display bool, tvType string, scaling } numFrames := endFrame - startFrame - fps, accuracy := CalcFPS(ftv, numFrames, duration.Seconds()) + fps, accuracy := CalcFPS(tv, numFrames, duration.Seconds()) output.Write([]byte(fmt.Sprintf("%.2f fps (%d frames in %.2f seconds) %.1f%%\n", fps, numFrames, duration.Seconds(), accuracy))) if profile { diff --git a/performance/fps.go b/performance/fps.go index 62f67cb1..9d2fd7e8 100644 --- a/performance/fps.go +++ b/performance/fps.go @@ -4,8 +4,8 @@ import "gopher2600/television" // CalcFPS takes the the number of frames and duration and returns the // frames-per-second and the accuracy of that value as a percentage. -func CalcFPS(ftv television.Television, numFrames int, duration float64) (fps float64, accuracy float64) { +func CalcFPS(tv television.Television, numFrames int, duration float64) (fps float64, accuracy float64) { fps = float64(numFrames) / duration - accuracy = 100 * float64(numFrames) / (duration * float64(ftv.GetSpec().FramesPerSecond)) + accuracy = 100 * float64(numFrames) / (duration * float64(tv.GetSpec().FramesPerSecond)) return fps, accuracy } diff --git a/playmode/play.go b/playmode/play.go index 6ef2dbff..d72dc9c3 100644 --- a/playmode/play.go +++ b/playmode/play.go @@ -5,23 +5,17 @@ import ( "gopher2600/cartridgeloader" "gopher2600/errors" "gopher2600/gui" - "gopher2600/gui/sdlplay" "gopher2600/hardware" "gopher2600/recorder" "gopher2600/setup" + "gopher2600/television" "os" "os/signal" "time" ) -func uniqueFilename(cartload cartridgeloader.Loader) string { - n := time.Now() - timestamp := fmt.Sprintf("%04d%02d%02d_%02d%02d%02d", n.Year(), n.Month(), n.Day(), n.Hour(), n.Minute(), n.Second()) - return fmt.Sprintf("recording_%s_%s", cartload.ShortName(), timestamp) -} - // Play sets the emulation running - without any debugging features -func Play(tvType string, scaling float32, stable bool, newRecording bool, cartload cartridgeloader.Loader) error { +func Play(tv television.Television, scr gui.GUI, stable bool, newRecording bool, cartload cartridgeloader.Loader) error { var transcript string // if supplied cartridge name is actually a playback file then set @@ -37,12 +31,7 @@ func Play(tvType string, scaling float32, stable bool, newRecording bool, cartlo cartload = cartridgeloader.Loader{} } - playtv, err := sdlplay.NewSdlPlay(tvType, scaling, nil) - if err != nil { - return errors.New(errors.PlayError, err) - } - - vcs, err := hardware.NewVCS(playtv) + vcs, err := hardware.NewVCS(tv) if err != nil { return errors.New(errors.PlayError, err) } @@ -53,24 +42,30 @@ func Play(tvType string, scaling float32, stable bool, newRecording bool, cartlo if newRecording { // new recording requested - transcript = uniqueFilename(cartload) + // create a unique filename + n := time.Now() + transcript = fmt.Sprintf("recording_%s_%s", + cartload.ShortName(), fmt.Sprintf("%04d%02d%02d_%02d%02d%02d", + n.Year(), n.Month(), n.Day(), n.Hour(), n.Minute(), n.Second())) + // prepare new recording rec, err := recorder.NewRecorder(transcript, vcs) if err != nil { return errors.New(errors.PlayError, err) } + // making sure we end the recording gracefully when we leave the function defer func() { rec.End() }() + // attach recorder to vcs peripherals, including the panel vcs.Ports.Player0.AttachTranscriber(rec) vcs.Ports.Player1.AttachTranscriber(rec) vcs.Panel.AttachTranscriber(rec) - // attaching cartridge after recorder and transcribers have been + // attach cartridge after recorder and transcribers have been // setup because we want to catch any setup events in the recording - err = setup.AttachCartridge(vcs, cartload) if err != nil { return errors.New(errors.PlayError, err) @@ -114,14 +109,14 @@ func Play(tvType string, scaling float32, stable bool, newRecording bool, cartlo // connect gui guiChannel := make(chan gui.Event, 2) - playtv.SetEventChannel(guiChannel) + scr.SetEventChannel(guiChannel) // request television visibility request := gui.ReqSetVisibilityStable if !stable { request = gui.ReqSetVisibility } - err = playtv.SetFeature(request, true) + err = scr.SetFeature(request, true) if err != nil { return errors.New(errors.PlayError, err) } @@ -141,7 +136,7 @@ func Play(tvType string, scaling float32, stable bool, newRecording bool, cartlo case gui.EventWindowClose: return false, nil case gui.EventKeyboard: - err = KeyboardEventHandler(ev.Data.(gui.EventDataKeyboard), playtv, vcs) + err = KeyboardEventHandler(ev.Data.(gui.EventDataKeyboard), scr, vcs) return err == nil, err } default: diff --git a/recorder/playback.go b/recorder/playback.go index b50ae61b..fc29d33a 100644 --- a/recorder/playback.go +++ b/recorder/playback.go @@ -15,7 +15,7 @@ import ( ) type event struct { - event peripherals.Event + event peripherals.Action frame int scanline int horizpos int @@ -130,7 +130,7 @@ func NewPlayback(transcript string) (*Playback, error) { msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldEvent+1], fieldSep))) return nil, errors.New(errors.PlaybackError, msg) } - event.event = peripherals.Event(n) + event.event = peripherals.Action(n) event.frame, err = strconv.Atoi(toks[fieldFrame]) if err != nil { @@ -180,16 +180,10 @@ func (plb *Playback) AttachToVCS(vcs *hardware.VCS) error { var err error - // create digesttv, piggybacking on the tv already being used by vcs; - // unless that tv is already a digesttv - switch tv := plb.vcs.TV.(type) { - case *screendigest.SHA1: - plb.digest = tv - default: - plb.digest, err = screendigest.NewSHA1(plb.vcs.TV.GetSpec().ID, plb.vcs.TV) - if err != nil { - return errors.New(errors.RecordingError, err) - } + // create digesttv using TV attached to VCS + plb.digest, err = screendigest.NewSHA1(plb.vcs.TV) + if err != nil { + return errors.New(errors.RecordingError, err) } // attach playback to controllers @@ -201,34 +195,34 @@ func (plb *Playback) AttachToVCS(vcs *hardware.VCS) error { } // GetInput implements peripherals.Controller interface -func (plb *Playback) GetInput(id peripherals.PeriphID) (peripherals.Event, error) { +func (plb *Playback) GetInput(id peripherals.PeriphID) (peripherals.Action, error) { // there's no events for this id at all seq := plb.sequences[id] // we've reached the end of the list of events for this id if seq.eventCt >= len(seq.events) { - return peripherals.NoEvent, nil + return peripherals.NoAction, nil } // get current state of the television frame, err := plb.vcs.TV.GetState(television.ReqFramenum) if err != nil { - return peripherals.NoEvent, errors.New(errors.PlaybackError, err) + return peripherals.NoAction, errors.New(errors.PlaybackError, err) } scanline, err := plb.vcs.TV.GetState(television.ReqScanline) if err != nil { - return peripherals.NoEvent, errors.New(errors.PlaybackError, err) + return peripherals.NoAction, errors.New(errors.PlaybackError, err) } horizpos, err := plb.vcs.TV.GetState(television.ReqHorizPos) if err != nil { - return peripherals.NoEvent, errors.New(errors.PlaybackError, err) + return peripherals.NoAction, errors.New(errors.PlaybackError, err) } // compare current state with the recording nextEvent := seq.events[seq.eventCt] if frame == nextEvent.frame && scanline == nextEvent.scanline && horizpos == nextEvent.horizpos { if nextEvent.hash != plb.digest.String() { - return peripherals.NoEvent, errors.New(errors.PlaybackHashError, fmt.Sprintf("line %d", nextEvent.line)) + return peripherals.NoAction, errors.New(errors.PlaybackHashError, fmt.Sprintf("line %d", nextEvent.line)) } seq.eventCt++ @@ -236,5 +230,5 @@ func (plb *Playback) GetInput(id peripherals.PeriphID) (peripherals.Event, error } // next event does not match - return peripherals.NoEvent, nil + return peripherals.NoAction, nil } diff --git a/recorder/recorder.go b/recorder/recorder.go index 1b267b2f..bebb53e9 100644 --- a/recorder/recorder.go +++ b/recorder/recorder.go @@ -32,7 +32,7 @@ func NewRecorder(transcript string, vcs *hardware.VCS) (*Recorder, error) { rec := &Recorder{vcs: vcs} // create digesttv, piggybacking on the tv already being used by vcs - rec.digest, err = screendigest.NewSHA1(vcs.TV.GetSpec().ID, vcs.TV) + rec.digest, err = screendigest.NewSHA1(vcs.TV) if err != nil { return nil, errors.New(errors.RecordingError, err) } @@ -77,7 +77,7 @@ func (rec *Recorder) End() error { } // Transcribe implements the Transcriber interface -func (rec *Recorder) Transcribe(id peripherals.PeriphID, event peripherals.Event) error { +func (rec *Recorder) Transcribe(id peripherals.PeriphID, event peripherals.Action) error { var err error // write header if it's not been written already @@ -90,7 +90,7 @@ func (rec *Recorder) Transcribe(id peripherals.PeriphID, event peripherals.Event } // don't do anything if event is the NoEvent - if event == peripherals.NoEvent { + if event == peripherals.NoAction { return nil } diff --git a/regression/frame.go b/regression/frame.go index 142a15c7..9c11d760 100644 --- a/regression/frame.go +++ b/regression/frame.go @@ -126,17 +126,17 @@ func (reg FrameRegression) CleanUp() error { func (reg *FrameRegression) regress(newRegression bool, output io.Writer, msg string) (bool, string, error) { output.Write([]byte(msg)) - stv, err := television.NewStellaTelevision(reg.TVtype) + tv, err := television.NewTelevision(reg.TVtype) if err != nil { return false, "", errors.New(errors.RegressionFrameError, err) } - dtv, err := screendigest.NewSHA1(reg.TVtype, stv) + dig, err := screendigest.NewSHA1(tv) if err != nil { return false, "", errors.New(errors.RegressionFrameError, err) } - vcs, err := hardware.NewVCS(dtv) + vcs, err := hardware.NewVCS(dig) if err != nil { return false, "", errors.New(errors.RegressionFrameError, err) } @@ -159,7 +159,7 @@ func (reg *FrameRegression) regress(newRegression bool, output io.Writer, msg st // add the starting state of the tv if reg.State { - state = append(state, stv.String()) + state = append(state, tv.String()) } // run emulation @@ -170,7 +170,7 @@ func (reg *FrameRegression) regress(newRegression bool, output io.Writer, msg st // store tv state at every step if reg.State { - state = append(state, stv.String()) + state = append(state, tv.String()) } return true, nil @@ -181,7 +181,7 @@ func (reg *FrameRegression) regress(newRegression bool, output io.Writer, msg st } if newRegression { - reg.screenDigest = dtv.String() + reg.screenDigest = dig.String() if reg.State { // create a unique filename @@ -256,7 +256,7 @@ func (reg *FrameRegression) regress(newRegression bool, output io.Writer, msg st } - if dtv.String() != reg.screenDigest { + if dig.String() != reg.screenDigest { return false, "screen digest mismatch", nil } diff --git a/regression/playback.go b/regression/playback.go index 319e2a4d..b20377b9 100644 --- a/regression/playback.go +++ b/regression/playback.go @@ -92,7 +92,12 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg return false, "", errors.New(errors.RegressionPlaybackError, err) } - tv, err := screendigest.NewSHA1(plb.TVtype, nil) + tv, err := television.NewTelevision(plb.TVtype) + if err != nil { + return false, "", errors.New(errors.RegressionFrameError, err) + } + + _, err = screendigest.NewSHA1(tv) if err != nil { return false, "", errors.New(errors.RegressionPlaybackError, err) } diff --git a/screendigest/digest.go b/screendigest/digest.go index 27ffdc69..8bb6e438 100644 --- a/screendigest/digest.go +++ b/screendigest/digest.go @@ -5,7 +5,6 @@ import ( "fmt" "gopher2600/errors" "gopher2600/television" - "strings" ) // SHA1 is an implementation of the television.Renderer interface with an @@ -26,30 +25,9 @@ const pixelDepth = 3 // NewSHA1 initialises a new instance of DigestTV. For convenience, the // television argument can be nil, in which case an instance of // StellaTelevision will be created. -func NewSHA1(tvType string, tv television.Television) (*SHA1, error) { - var err error - +func NewSHA1(tv television.Television) (*SHA1, error) { // set up digest tv - dig := new(SHA1) - - // create or attach television implementation - if tv == nil { - dig.Television, err = television.NewStellaTelevision(tvType) - if err != nil { - return nil, err - } - } else { - // check that the quoted tvType matches the specification of the - // supplied BasicTelevision instance. we don't really need this but - // becuase we're implying that tvType is required, even when an - // instance of BasicTelevision has been supplied, the caller may be - // expecting an error - tvType = strings.ToUpper(tvType) - if tvType != "AUTO" && tvType != tv.GetSpec().ID { - return nil, errors.New(errors.ScreenDigest, "trying to piggyback a tv of a different spec") - } - dig.Television = tv - } + dig := &SHA1{Television: tv} // register ourselves as a television.Renderer dig.AddPixelRenderer(dig) @@ -78,7 +56,7 @@ func (dig *SHA1) Resize(_, _ int) error { l := len(dig.digest) // alloscate enough pixels for entire frame - l += ((television.ClocksPerScanline + 1) * (dig.GetSpec().ScanlinesTotal + 1) * pixelDepth) + l += ((television.HorizClksScanline + 1) * (dig.GetSpec().ScanlinesTotal + 1) * pixelDepth) dig.pixels = make([]byte, l) return nil @@ -106,7 +84,7 @@ func (dig *SHA1) NewScanline(scanline int) error { func (dig *SHA1) SetPixel(x, y int, red, green, blue byte, vblank bool) error { // preserve the first few bytes for a chained fingerprint i := len(dig.digest) - i += television.ClocksPerScanline * y * pixelDepth + i += television.HorizClksScanline * y * pixelDepth i += x * pixelDepth if i <= len(dig.pixels)-pixelDepth { diff --git a/television/protocol.go b/television/protocol.go new file mode 100644 index 00000000..52184ea4 --- /dev/null +++ b/television/protocol.go @@ -0,0 +1,155 @@ +package television + +import ( + "gopher2600/hardware/tia/audio" +) + +// Television defines the operations that can be performed on the conceptual +// television. Note that the television implementation itself does not present +// any information, either visually or sonically. Instead, PixelRenderers and +// AudioMixers are added to perform those tasks. +type Television interface { + String() string + + // Reset the television to an initial state + Reset() error + + // AddPixelRenderer registers an (additional) implementation of PixelRenderer + AddPixelRenderer(PixelRenderer) + + // AddAudioMixer registers an (additional) implementation of AudioMixer + AddAudioMixer(AudioMixer) + + Signal(SignalAttributes) error + + // Returns the value of the requested state. eg. the current scanline. + GetState(StateReq) (int, error) + + // Returns the television's current specification. Renderers should use + // GetSpec() rather than keeping a private pointer to the specification. + GetSpec() *Specification + + // IsStable returns true if the television thinks the image being sent by + // the VCS is stable + IsStable() bool +} + +// PixelRenderer implementations displays, or otherwise works with, visal +// information from a television +// +// examples of renderers that display visual information: +// * SDLPlay +// * ImageTV +// +// examples of renderers that do not display visual information but only work +// with it: +// * DigestTV +// +// PixelRenderer implementations find it convenient to maintain a reference to +// the parent Television implementation and maybe even embed the Television +// interface. ie. +// +// type ExampleTV struct { +// television.Television +// +// ... +// } +type PixelRenderer interface { + // Resize is called when the television implementation detects that extra + // scanlines are required in the display. + // + // It may be called when television specification has changed. Renderers + // should use GetSpec() rather than keeping a private pointer to the + // specification. + // + // Renderers should use the values sent by the Resize() function, rather + // than the equivalent values in the specification. Unless of course, the + // renderer is intended to be strict about specification accuracy. + // + // Renderers should also make sure that any data structures that depend on + // the specification being used are still adequate. + Resize(topScanline, visibleScanlines int) error + + // NewFrame and NewScanline are called at the start of the frame/scanline + NewFrame(frameNum int) error + NewScanline(scanline int) error + + // setPixel() and setAltPixel() are called every cycle regardless of the + // state of VBLANK and HBLANK. + // + // things to consider: + // + // o the x argument is measured from zero so renderers should decide how to + // handle pixels of during the HBLANK (x < ClocksPerHBLANK) + // + // o the y argument is also measure from zero but because VBLANK can be + // turned on at any time there's no easy test. the VBLANK flag is sent to + // help renderers decide what to do. + // + // o for renderers that are producing an accurate visual image, the pixel + // should always be set to video black if VBLANK is on. + // + // some renderers however, may find it useful to set the pixel to the RGB + // value regardless of VBLANK. for example, DigestTV does this. + // + // a vey important note is that some ROMs use VBLANK to control pixel + // color within the visible display area. ROMs affected: + // + // * Custer's Revenge + // * Ladybug + // + SetPixel(x, y int, red, green, blue byte, vblank bool) error + SetAltPixel(x, y int, red, green, blue byte, vblank bool) error +} + +// AudioMixer implementations work with sound; most probably playing it. +type AudioMixer interface { + SetAudio(audio audio.Audio) error +} + +// SignalAttributes represents the data sent to the television +type SignalAttributes struct { + VSync bool + VBlank bool + CBurst bool + HSync bool + Pixel ColorSignal + + // AltPixel allows the emulator to set an alternative color for each pixel + // - used to signal the debug color in addition to the regular color + // - arguable that this be sent as some sort of meta-signal + AltPixel ColorSignal + + // the HSyncSimple attribute is not part of the real TV spec. The signal + // for a real flyback is the HSync signal (held for 8 color clocks). + // however, this results in a confusing way of counting pixels - confusing + // at least to people who are used to the Stella method of counting. + // + // if we were to use HSync to detect a new scanline then we have to treat + // the front porch and back porch separately. the convenient HSyncSimple + // attribute effectively pushes the front and back porches together meaning + // we can count from -68 to 159 - the same as Stella. this is helpful when + // A/B testing. + // + // the TIA emulation sends both HSync and HSyncSimple signals. television + // implementations can use either, it doesn't really make any difference + // except to debugging information. the "basic" television implementation + // uses HSyncSimple instead of HSync + HSyncSimple bool + + // audio signal is just the content of the VCS audio registers. for now, + // sounds is generated/mixed by the television or gui implementation + Audio audio.Audio + UpdateAudio bool +} + +// StateReq is used to identify which television attribute is being asked +// with the GetState() function +type StateReq int + +// list of valid state requests +const ( + ReqFramenum StateReq = iota + ReqScanline + ReqHorizPos +) diff --git a/television/specifications.go b/television/specifications.go index 1fcda883..8636e96e 100644 --- a/television/specifications.go +++ b/television/specifications.go @@ -2,67 +2,99 @@ package television // Specification is used to define the two television specifications type Specification struct { - ID string + ID string + Colors colors - ScanlinesPerVSync int - ScanlinesPerVBlank int - ScanlinesPerVisible int - ScanlinesPerOverscan int - ScanlinesTotal int + // the number of scanlines the 2600 Programmer's guide recommends for the + // top/bottom parts of the screen: + // + // "A typical frame will consists of 3 vertical sync (VSYNC) lines*, 37 vertical + // blank (VBLANK) lines, 192 TV picture lines, and 30 overscan lines. Atari’s + // research has shown that this pattern will work on all types of TV sets." + // + // the above figures are in reference to the NTSC protocol + scanlinesVSync int + scanlinesVBlank int + ScanlinesVisible int + scanlinesOverscan int + // the total number of scanlines for the entire frame is the sum of the + // four individual portions + ScanlinesTotal int + + // the scanline at which the VBLANK should be turned off (Top) and + // turned back on again (Bottom). the period between the top and bottom + // scanline is the visible portion of the screen. + // + // in practice, the VCS can turn VBLANK on and off at any time; what the + // two values below represent what "Atari's research" has shown to be safe. + // by definition this means that: + // + // Top = VSync + Vblank + // + // Bottom = Top + Visible + // + // or + // + // Bottom = Total - Overscan ScanlineTop int ScanlineBottom int - Colors colors - + // the number of frames per second required by the specification FramesPerSecond int SecondsPerFrame float64 - // AspectBias transforms the scaling factor for the X axis. + // 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 + // aspect bias AspectBias float32 } -// ClocksPerHblank is the same for all tv specifications -const ClocksPerHblank = 68 +// "Each scan lines starts with 68 clock counts of horizontal blank (not seen on +// the TV screen) followed by 160 clock counts to fully scan one line of TV +// picture. When the electron beam reaches the end of a scan line, it returns +// to the left side of the screen, waits for the 68 horizontal blank clock +// counts, and proceeds to draw the next line below." +// +// Horizontal clock counts are the same for both TV specificationst +const ( + HorizClksHBlank = 68 + HorizClksVisible = 160 + HorizClksScanline = 228 +) -// ClocksPerVisible is the same for all tv specifications -const ClocksPerVisible = 160 - -// ClocksPerScanline is the same for all tv specifications -const ClocksPerScanline = 228 - -// SpecNTSC is the specification for NTSC television typee +// SpecNTSC is the specification for NTSC television types var SpecNTSC *Specification -// SpecPAL is the specification for PAL television typee +// SpecPAL is the specification for PAL television types var SpecPAL *Specification func init() { SpecNTSC = new(Specification) SpecNTSC.ID = "NTSC" - SpecNTSC.ScanlinesPerVSync = 3 - SpecNTSC.ScanlinesPerVBlank = 37 - SpecNTSC.ScanlinesPerVisible = 192 - SpecNTSC.ScanlinesPerOverscan = 30 + SpecNTSC.Colors = colorsNTSC + SpecNTSC.scanlinesVSync = 3 + SpecNTSC.scanlinesVBlank = 37 + SpecNTSC.ScanlinesVisible = 192 + SpecNTSC.scanlinesOverscan = 30 SpecNTSC.ScanlinesTotal = 262 - SpecNTSC.ScanlineTop = SpecNTSC.ScanlinesPerVBlank + SpecNTSC.ScanlinesPerVSync - SpecNTSC.ScanlineBottom = SpecNTSC.ScanlinesTotal - SpecNTSC.ScanlinesPerOverscan + SpecNTSC.ScanlineTop = SpecNTSC.scanlinesVBlank + SpecNTSC.scanlinesVSync + SpecNTSC.ScanlineBottom = SpecNTSC.ScanlinesTotal - SpecNTSC.scanlinesOverscan SpecNTSC.FramesPerSecond = 60 SpecNTSC.SecondsPerFrame = 1.0 / float64(SpecNTSC.FramesPerSecond) - SpecNTSC.Colors = colorsNTSC SpecPAL = new(Specification) SpecPAL.ID = "PAL" - SpecPAL.ScanlinesPerVSync = 3 - SpecPAL.ScanlinesPerVBlank = 45 - SpecPAL.ScanlinesPerVisible = 228 - SpecPAL.ScanlinesPerOverscan = 36 + SpecPAL.Colors = colorsPAL + SpecPAL.scanlinesVSync = 3 + SpecPAL.scanlinesVBlank = 45 + SpecPAL.ScanlinesVisible = 228 + SpecPAL.scanlinesOverscan = 36 SpecPAL.ScanlinesTotal = 312 - SpecPAL.ScanlineTop = SpecPAL.ScanlinesPerVBlank + SpecPAL.ScanlinesPerVSync - SpecPAL.ScanlineBottom = SpecPAL.ScanlinesTotal - SpecPAL.ScanlinesPerOverscan + SpecPAL.ScanlineTop = SpecPAL.scanlinesVBlank + SpecPAL.scanlinesVSync + SpecPAL.ScanlineBottom = SpecPAL.ScanlinesTotal - SpecPAL.scanlinesOverscan SpecPAL.FramesPerSecond = 50 SpecPAL.SecondsPerFrame = 1.0 / float64(SpecPAL.FramesPerSecond) - SpecPAL.Colors = colorsPAL // AaspectBias transforms the scaling factor for the X axis. // values taken from Stella emualtor. useful for A/B testing diff --git a/television/stella.go b/television/stella.go deleted file mode 100644 index 8a8e7415..00000000 --- a/television/stella.go +++ /dev/null @@ -1,350 +0,0 @@ -package television - -import ( - "fmt" - "gopher2600/errors" - "strings" -) - -// StellaTelevision is the minimalist implementation of the Television -// interface. It is so called because the reporting of the TV state, via -// GetState(), is meant to mirror exactly the state as reported by the stella -// emulator. The intention is to make it easier to perform A/B testing. -// -// To make the state reporting as intuitive as possible, StellaTelevision makes -// use of the HSyncSimple sigal attribute (see SignalAttributes type in the -// television package for details). Consequently, calls to NewScanline() for -// any attached renderers, are made when the HSyncSimple signal is recieved. -// This will have an effect on how the renderer displays off screen information -// (if it chooses to that is). -type StellaTelevision struct { - // television specification (NTSC or PAL) - spec *Specification - - // auto flag indicates that the tv type/specification should switch if it - // appears to be outside of the current spec. - // - // in practice this means that if auto is true then we start with the NTSC - // spec and move to PAL if the number of scanlines exceeds the NTSC maximum - auto bool - - // state of the television - // - the current horizontal position. the position where the next pixel will be - // drawn. also used to check we're receiving the correct signals at the - // correct time. - horizPos int - // - the current frame - frameNum int - // - the current scanline number - scanline int - - // record of signal attributes from the last call to Signal() - prevSignal SignalAttributes - - // vsyncCount records the number of consecutive colorClocks the vsync signal - // has been sustained. we use this to help correctly implement vsync. - vsyncCount int - vsyncPos int - - // list of renderer implementations to consult - renderers []PixelRenderer - - // list of audio mixers to consult - mixers []AudioMixer - - // the following values are used for stability detection. we could possibly - // define a separate type for all of these. - - // top and bottom of screen as detected by vblank/color signal - top int - bottom int - - // new top and bottom values if stability threshold is met - speculativeTop int - speculativeBottom int - - // top and bottom as reckoned by the current frame - reset at the moment - // when a new frame is detected - thisTop int - thisBottom int - - // a frame has to be stable (speculative top and bottom unchanged) for a - // number of frames (stable threshold) before we accept that it is a true - // representation of frame dimensions - stability int -} - -// the number of frames that (speculative) top and bottom values must be steady -// before we accept the frame characteristics -const stabilityThreshold = 5 - -// NewStellaTelevision creates a new instance of StellaTelevision for a -// minimalist implementation of a televsion for the VCS emulation -func NewStellaTelevision(tvType string) (*StellaTelevision, error) { - btv := new(StellaTelevision) - - switch strings.ToUpper(tvType) { - case "NTSC": - btv.spec = SpecNTSC - case "PAL": - btv.spec = SpecPAL - case "AUTO": - btv.spec = SpecNTSC - btv.auto = true - default: - return nil, errors.New(errors.StellaTelevision, fmt.Sprintf("unsupported tv type (%s)", tvType)) - } - - // empty list of renderers - btv.renderers = make([]PixelRenderer, 0) - - // initialise TVState - err := btv.Reset() - if err != nil { - return nil, err - } - - return btv, nil -} - -func (btv StellaTelevision) String() string { - s := strings.Builder{} - s.WriteString(fmt.Sprintf("FR=%d SL=%d", btv.frameNum, btv.scanline)) - s.WriteString(fmt.Sprintf(" HP=%d", btv.horizPos)) - return s.String() -} - -// AddPixelRenderer implements the Television interface -func (btv *StellaTelevision) AddPixelRenderer(r PixelRenderer) { - btv.renderers = append(btv.renderers, r) -} - -// AddAudioMixer implements the Television interface -func (btv *StellaTelevision) AddAudioMixer(m AudioMixer) { - btv.mixers = append(btv.mixers, m) -} - -// Reset implements the Television interface -func (btv *StellaTelevision) Reset() error { - btv.horizPos = -ClocksPerHblank - btv.frameNum = 0 - btv.scanline = 0 - btv.vsyncCount = 0 - btv.prevSignal = SignalAttributes{Pixel: VideoBlack} - - btv.top = btv.spec.ScanlineTop - btv.bottom = btv.spec.ScanlineBottom - - return nil -} - -// Signal implements the Television interface -func (btv *StellaTelevision) Signal(sig SignalAttributes) error { - // the following condition detects a new scanline by looking for the - // non-textbook HSyncSimple signal - // - // see SignalAttributes type definition for notes about the HSyncSimple - // attribute - if sig.HSyncSimple && !btv.prevSignal.HSyncSimple { - btv.horizPos = -ClocksPerHblank - btv.scanline++ - - if btv.scanline <= btv.spec.ScanlinesTotal { - // when observing Stella we can see that on the first frame (frame - // number zero) a new frame is triggered when the scanline reaches - // 51. it does this with every ROM and regardless of what signals - // have been sent. - // - // I'm not sure why it does this but we emulate the behaviour here - // in order to facilitate A/B testing. - if btv.frameNum == 0 && btv.scanline > 50 { - btv.scanline = 0 - btv.frameNum++ - - // notify renderers of new frame - for f := range btv.renderers { - err := btv.renderers[f].NewFrame(btv.frameNum) - if err != nil { - return err - } - } - } else { - // notify renderers of new scanline - for f := range btv.renderers { - err := btv.renderers[f].NewScanline(btv.scanline) - if err != nil { - return err - } - } - } - } else { - // repeat last scanline over and over - btv.scanline = btv.spec.ScanlinesTotal - } - - } else { - btv.horizPos++ - if btv.horizPos > ClocksPerScanline { - return errors.New(errors.StellaTelevision, "no flyback signal") - } - } - - // simple vsync implementation. when compared to the HSync detection above, - // the following is correct (front porch at the end of the display and back - // porch at the beginning). it is also in keeping with how Stella counts - // scanlines, meaning A/B testing is relatively straightforward. - if sig.VSync { - // if this a new vsync sequence note the horizontal position - if !btv.prevSignal.VSync { - btv.vsyncPos = btv.horizPos - } - // bump the vsync count whenever vsync is set - btv.vsyncCount++ - } else if btv.prevSignal.VSync { - // if vsync has just be turned off then check that it has been held for - // the requisite number of scanlines for a new frame to be started - if btv.vsyncCount >= btv.spec.ScanlinesPerVSync { - err := btv.newFrame() - if err != nil { - return err - } - } - - // reset vsync counter when vsync signal is dropped - btv.vsyncCount = 0 - } - - // record the current signal settings so they can be used for reference - btv.prevSignal = sig - - // current coordinates - x := btv.horizPos + ClocksPerHblank - y := btv.scanline - - // decode color using the regular color signal - red, green, blue := getColor(btv.spec, sig.Pixel) - for f := range btv.renderers { - err := btv.renderers[f].SetPixel(x, y, red, green, blue, sig.VBlank) - if err != nil { - return err - } - } - - // push screen boundaries outward using vblank and color signal to help us - if !sig.VBlank && red != 0 && green != 0 && blue != 0 { - if btv.scanline < btv.thisTop { - btv.thisTop = btv.scanline - } - if btv.scanline > btv.thisBottom { - btv.thisBottom = btv.scanline - } - } - - // decode color using the alternative color signal - red, green, blue = getAltColor(sig.AltPixel) - for f := range btv.renderers { - err := btv.renderers[f].SetAltPixel(x, y, red, green, blue, sig.VBlank) - if err != nil { - return err - } - } - - // mix audio on UpdateAudio signal - if sig.UpdateAudio { - for f := range btv.mixers { - err := btv.mixers[f].SetAudio(sig.Audio) - if err != nil { - return err - } - } - } - - return nil -} - -func (btv *StellaTelevision) stabilise() (bool, error) { - if btv.frameNum <= 1 || (btv.thisTop == btv.top && btv.thisBottom == btv.bottom) { - return false, nil - } - - // if top and bottom has changed this frame update speculative values - if btv.thisTop != btv.speculativeTop || btv.thisBottom != btv.speculativeBottom { - btv.speculativeTop = btv.thisTop - btv.speculativeBottom = btv.thisBottom - return false, nil - } - - // increase stability value until we reach threshold - if !btv.IsStable() { - btv.stability++ - return false, nil - } - - // accept speculative values - btv.top = btv.speculativeTop - btv.bottom = btv.speculativeBottom - - if btv.spec == SpecNTSC && btv.bottom-btv.top >= SpecPAL.ScanlinesPerVisible { - btv.spec = SpecPAL - - // reset top/bottom to ideals of new spec. they may of course be - // pushed outward in subsequent frames - btv.top = btv.spec.ScanlineTop - btv.bottom = btv.spec.ScanlineBottom - } - - for f := range btv.renderers { - err := btv.renderers[f].Resize(btv.top, btv.bottom-btv.top+1) - if err != nil { - return false, err - } - } - return true, nil -} - -func (btv *StellaTelevision) newFrame() error { - _, err := btv.stabilise() - if err != nil { - return err - } - - // new frame - btv.frameNum++ - btv.scanline = 0 - btv.thisTop = btv.top - btv.thisBottom = btv.bottom - - // call new frame for all renderers - for f := range btv.renderers { - err = btv.renderers[f].NewFrame(btv.frameNum) - if err != nil { - return err - } - } - - return nil -} - -// GetState implements the Television interface -func (btv *StellaTelevision) GetState(request StateReq) (int, error) { - switch request { - default: - return 0, errors.New(errors.UnknownTVRequest, request) - case ReqFramenum: - return btv.frameNum, nil - case ReqScanline: - return btv.scanline, nil - case ReqHorizPos: - return btv.horizPos, nil - } -} - -// GetSpec implements the Television interface -func (btv StellaTelevision) GetSpec() *Specification { - return btv.spec -} - -// IsStable implements the Television interface -func (btv StellaTelevision) IsStable() bool { - return btv.stability >= stabilityThreshold -} diff --git a/television/television.go b/television/television.go index 788b7eaa..818befc2 100644 --- a/television/television.go +++ b/television/television.go @@ -1,150 +1,341 @@ package television -import "gopher2600/hardware/tia/audio" - -// Television defines the operations that can be performed on the conceptual -// television. Implementations should not actually present the information, -// either visually or sonically. Instead, Renderers and Mixers can be added to -// perform those tasks. -// -// Note that for convenience, many television implementations "double-up" as -// Renderer interfaces. In these instances, the television will call -// AddRenderer() with itself as an argument. -type Television interface { - String() string - AddPixelRenderer(PixelRenderer) - AddAudioMixer(AudioMixer) - Reset() error - Signal(SignalAttributes) error - - // Returns the value of the requested state. eg. the current scanline. - GetState(StateReq) (int, error) - - // Returns the television's current specification. Renderers should use - // GetSpec() rather than keeping a private pointer to the specification. - GetSpec() *Specification - - // IsStable returns true if the television thinks the image being sent by - // the VCS is stable - IsStable() bool -} - -// PixelRenderer implementations displays, or otherwise works with, visal -// information from a television -// -// examples of renderers that display visual information: -// * SDLPlay -// * ImageTV -// -// examples of renderers that do not display visual information but only work -// with it: -// * DigestTV -// -// PixelRenderer implementations find it convenient to maintain a reference to -// the parent Television implementation and maybe even embed the Television -// interface. ie. -// -// type ExampleTV struct { -// television.Television -// -// ... -// } -type PixelRenderer interface { - // Resize is called when the television implementation detects that extra - // scanlines are required in the display. - // - // It may be called when television specification has changed. Renderers - // should use GetSpec() rather than keeping a private pointer to the - // specification. - // - // Renderers should use the values sent by the Resize() function, rather - // than the equivalent values in the specification. Unless of course, the - // renderer is intended to be strict about specification accuracy. - // - // Renderers should also make sure that any data structures that depend on - // the specification being used are still adequate. - Resize(topScanline, visibleScanlines int) error - - // NewFrame and NewScanline are called at the start of the frame/scanline - NewFrame(frameNum int) error - NewScanline(scanline int) error - - // setPixel() and setAltPixel() are called every cycle regardless of the - // state of VBLANK and HBLANK. - // - // things to consider: - // - // o the x argument is measured from zero so renderers should decide how to - // handle pixels of during the HBLANK (x < ClocksPerHBLANK) - // - // o the y argument is also measure from zero but because VBLANK can be - // turned on at any time there's no easy test. the VBLANK flag is sent to - // help renderers decide what to do. - // - // o for renderers that are producing an accurate visual image, the pixel - // should always be set to video black if VBLANK is on. - // - // some renderers however, may find it useful to set the pixel to the RGB - // value regardless of VBLANK. for example, DigestTV does this. - // - // a vey important note is that some ROMs use VBLANK to control pixel - // color within the visible display area. ROMs affected: - // - // * Custer's Revenge - // * Ladybug - // - SetPixel(x, y int, red, green, blue byte, vblank bool) error - SetAltPixel(x, y int, red, green, blue byte, vblank bool) error -} - -// AudioMixer implementations work with sound; most probably playing it. -type AudioMixer interface { - SetAudio(audio audio.Audio) error -} - -// SignalAttributes represents the data sent to the television -type SignalAttributes struct { - VSync bool - VBlank bool - CBurst bool - HSync bool - Pixel ColorSignal - - // AltPixel allows the emulator to set an alternative color for each pixel - // - used to signal the debug color in addition to the regular color - // - arguable that this be sent as some sort of meta-signal - AltPixel ColorSignal - - // the HSyncSimple attribute is not part of the real TV spec. The signal - // for a real flyback is the HSync signal (held for 8 color clocks). - // however, this results in a confusing way of counting pixels - confusing - // at least to people who are used to the Stella method of counting. - // - // if we were to use HSync to detect a new scanline then we have to treat - // the front porch and back porch separately. the convenient HSyncSimple - // attribute effectively pushes the front and back porches together meaning - // we can count from -68 to 159 - the same as Stella. this is helpful when - // A/B testing. - // - // the TIA emulation sends both HSync and HSyncSimple signals. television - // implementations can use either, it doesn't really make any difference - // except to debugging information. the "basic" television implementation - // uses HSyncSimple instead of HSync - HSyncSimple bool - - // audio signal is just the content of the VCS audio registers. for now, - // sounds is generated/mixed by the television or gui implementation - Audio audio.Audio - UpdateAudio bool -} - -// StateReq is used to identify which television attribute is being asked -// with the GetState() function -type StateReq int - -// list of valid state requests -const ( - ReqFramenum StateReq = iota - ReqScanline - ReqHorizPos +import ( + "fmt" + "gopher2600/errors" + "strings" ) + +// television is a reference implementation of the Television interface. In all +// honesty, it's most likely the only implementation required. +type television struct { + // television specification (NTSC or PAL) + spec *Specification + + // auto flag indicates that the tv type/specification should switch if it + // appears to be outside of the current spec. + // + // in practice this means that if auto is true then we start with the NTSC + // spec and move to PAL if the number of scanlines exceeds the NTSC maximum + auto bool + + // state of the television + // - the current horizontal position. the position where the next pixel will be + // drawn. also used to check we're receiving the correct signals at the + // correct time. + horizPos int + // - the current frame + frameNum int + // - the current scanline number + scanline int + + // record of signal attributes from the last call to Signal() + prevSignal SignalAttributes + + // vsyncCount records the number of consecutive colorClocks the vsync signal + // has been sustained. we use this to help correctly implement vsync. + vsyncCount int + vsyncPos int + + // list of renderer implementations to consult + renderers []PixelRenderer + + // list of audio mixers to consult + mixers []AudioMixer + + // the following values are used for stability detection. we could possibly + // define a separate type for all of these. + + // top and bottom of screen as detected by vblank/color signal + top int + bottom int + + // new top and bottom values if stability threshold is met + speculativeTop int + speculativeBottom int + + // top and bottom as reckoned by the current frame - reset at the moment + // when a new frame is detected + thisTop int + thisBottom int + + // a frame has to be stable (speculative top and bottom unchanged) for a + // number of frames (stable threshold) before we accept that it is a true + // representation of frame dimensions + stability int +} + +// the number of frames that (speculative) top and bottom values must be steady +// before we accept the frame characteristics +const stabilityThreshold = 5 + +// NewTelevision creates a new instance of StellaTelevision for a +// minimalist implementation of a televsion for the VCS emulation +func NewTelevision(tvType string) (Television, error) { + tv := new(television) + + switch strings.ToUpper(tvType) { + case "NTSC": + tv.spec = SpecNTSC + case "PAL": + tv.spec = SpecPAL + case "AUTO": + tv.spec = SpecNTSC + tv.auto = true + default: + return nil, errors.New(errors.Television, fmt.Sprintf("unsupported tv type (%s)", tvType)) + } + + // empty list of renderers + tv.renderers = make([]PixelRenderer, 0) + + // initialise TVState + err := tv.Reset() + if err != nil { + return nil, err + } + + return tv, nil +} + +func (tv television) String() string { + s := strings.Builder{} + s.WriteString(fmt.Sprintf("FR=%d SL=%d", tv.frameNum, tv.scanline)) + s.WriteString(fmt.Sprintf(" HP=%d", tv.horizPos)) + return s.String() +} + +// AddPixelRenderer implements the Television interface +func (tv *television) AddPixelRenderer(r PixelRenderer) { + tv.renderers = append(tv.renderers, r) +} + +// AddAudioMixer implements the Television interface +func (tv *television) AddAudioMixer(m AudioMixer) { + tv.mixers = append(tv.mixers, m) +} + +// Reset implements the Television interface +func (tv *television) Reset() error { + tv.horizPos = -HorizClksHBlank + tv.frameNum = 0 + tv.scanline = 0 + tv.vsyncCount = 0 + tv.prevSignal = SignalAttributes{Pixel: VideoBlack} + + tv.top = tv.spec.ScanlineTop + tv.bottom = tv.spec.ScanlineBottom + + return nil +} + +// Signal implements the Television interface +func (tv *television) Signal(sig SignalAttributes) error { + // the following condition detects a new scanline by looking for the + // non-textbook HSyncSimple signal + // + // see SignalAttributes type definition for notes about the HSyncSimple + // attribute + if sig.HSyncSimple && !tv.prevSignal.HSyncSimple { + tv.horizPos = -HorizClksHBlank + tv.scanline++ + + if tv.scanline <= tv.spec.ScanlinesTotal { + // when observing Stella we can see that on the first frame (frame + // number zero) a new frame is triggered when the scanline reaches + // 51. it does this with every ROM and regardless of what signals + // have been sent. + // + // I'm not sure why it does this but we emulate the behaviour here + // in order to facilitate A/B testing. + if tv.frameNum == 0 && tv.scanline > 50 { + tv.scanline = 0 + tv.frameNum++ + + // notify renderers of new frame + for f := range tv.renderers { + err := tv.renderers[f].NewFrame(tv.frameNum) + if err != nil { + return err + } + } + } else { + // notify renderers of new scanline + for f := range tv.renderers { + err := tv.renderers[f].NewScanline(tv.scanline) + if err != nil { + return err + } + } + } + } else { + // repeat last scanline over and over + tv.scanline = tv.spec.ScanlinesTotal + } + + } else { + tv.horizPos++ + if tv.horizPos > HorizClksScanline { + return errors.New(errors.Television, "no flyback signal") + } + } + + // simple vsync implementation. when compared to the HSync detection above, + // the following is correct (front porch at the end of the display and back + // porch at the beginning). it is also in keeping with how Stella counts + // scanlines, meaning A/B testing is relatively straightforward. + if sig.VSync { + // if this a new vsync sequence note the horizontal position + if !tv.prevSignal.VSync { + tv.vsyncPos = tv.horizPos + } + // bump the vsync count whenever vsync is set + tv.vsyncCount++ + } else if tv.prevSignal.VSync { + // if vsync has just be turned off then check that it has been held for + // the requisite number of scanlines for a new frame to be started + if tv.vsyncCount >= tv.spec.scanlinesVSync { + err := tv.newFrame() + if err != nil { + return err + } + } + + // reset vsync counter when vsync signal is dropped + tv.vsyncCount = 0 + } + + // current coordinates + x := tv.horizPos + HorizClksHBlank + y := tv.scanline + + // decode color using the alternative color signal + red, green, blue := getAltColor(sig.AltPixel) + for f := range tv.renderers { + err := tv.renderers[f].SetAltPixel(x, y, red, green, blue, sig.VBlank) + if err != nil { + return err + } + } + + // decode color using the regular color signal + red, green, blue = getColor(tv.spec, sig.Pixel) + for f := range tv.renderers { + err := tv.renderers[f].SetPixel(x, y, red, green, blue, sig.VBlank) + if err != nil { + return err + } + } + + // push screen boundaries outward using vblank and color signal to help us + if !sig.VBlank && red != 0 && green != 0 && blue != 0 { + if tv.scanline < tv.thisTop { + tv.thisTop = tv.scanline + } + if tv.scanline > tv.thisBottom { + tv.thisBottom = tv.scanline + } + } + + // mix audio on UpdateAudio signal + if sig.UpdateAudio { + for f := range tv.mixers { + err := tv.mixers[f].SetAudio(sig.Audio) + if err != nil { + return err + } + } + } + + // record the current signal settings so they can be used for reference + tv.prevSignal = sig + + return nil +} + +func (tv *television) stabilise() (bool, error) { + if tv.frameNum <= 1 || (tv.thisTop == tv.top && tv.thisBottom == tv.bottom) { + return false, nil + } + + // if top and bottom has changed this frame update speculative values + if tv.thisTop != tv.speculativeTop || tv.thisBottom != tv.speculativeBottom { + tv.speculativeTop = tv.thisTop + tv.speculativeBottom = tv.thisBottom + return false, nil + } + + // increase stability value until we reach threshold + if !tv.IsStable() { + tv.stability++ + return false, nil + } + + // accept speculative values + tv.top = tv.speculativeTop + tv.bottom = tv.speculativeBottom + + if tv.spec == SpecNTSC && tv.bottom-tv.top >= SpecPAL.ScanlinesVisible { + tv.spec = SpecPAL + + // reset top/bottom to ideals of new spec. they may of course be + // pushed outward in subsequent frames + tv.top = tv.spec.ScanlineTop + tv.bottom = tv.spec.ScanlineBottom + } + + for f := range tv.renderers { + err := tv.renderers[f].Resize(tv.top, tv.bottom-tv.top+1) + if err != nil { + return false, err + } + } + return true, nil +} + +func (tv *television) newFrame() error { + _, err := tv.stabilise() + if err != nil { + return err + } + + // new frame + tv.frameNum++ + tv.scanline = 0 + tv.thisTop = tv.top + tv.thisBottom = tv.bottom + + // call new frame for all renderers + for f := range tv.renderers { + err = tv.renderers[f].NewFrame(tv.frameNum) + if err != nil { + return err + } + } + + return nil +} + +// GetState implements the Television interface +func (tv *television) GetState(request StateReq) (int, error) { + switch request { + default: + return 0, errors.New(errors.UnknownTVRequest, request) + case ReqFramenum: + return tv.frameNum, nil + case ReqScanline: + return tv.scanline, nil + case ReqHorizPos: + return tv.horizPos, nil + } +} + +// GetSpec implements the Television interface +func (tv television) GetSpec() *Specification { + return tv.spec +} + +// IsStable implements the Television interface +func (tv television) IsStable() bool { + return tv.stability >= stabilityThreshold +} diff --git a/test/gopher2600_test.go b/test/gopher2600_test.go index 7367e60e..b4a50630 100644 --- a/test/gopher2600_test.go +++ b/test/gopher2600_test.go @@ -6,20 +6,26 @@ import ( "gopher2600/gui" "gopher2600/gui/sdldebug" "gopher2600/hardware" + "gopher2600/television" "testing" ) func BenchmarkSDL(b *testing.B) { var err error - tv, err := sdldebug.NewSdlDebug("NTSC", 1.0, nil) + tv, err := television.NewTelevision("AUTO") if err != nil { panic(fmt.Errorf("error preparing television: %s", err)) } - err = tv.SetFeature(gui.ReqSetVisibility, true) + scr, err := sdldebug.NewSdlDebug(tv, 1.0) if err != nil { - panic(fmt.Errorf("error preparing television: %s", err)) + panic(fmt.Errorf("error preparing screen: %s", err)) + } + + err = scr.SetFeature(gui.ReqSetVisibility, true) + if err != nil { + panic(fmt.Errorf("error preparing screen: %s", err)) } vcs, err := hardware.NewVCS(tv) diff --git a/web2600/src/canvas.go b/web2600/src/canvas.go index 3e738f48..3874000a 100644 --- a/web2600/src/canvas.go +++ b/web2600/src/canvas.go @@ -15,8 +15,8 @@ const pixelWidth = 2 const horizScale = 2 const vertScale = 2 -// CanvasTV implements television.PixelRenderer -type CanvasTV struct { +// Canvas implements television.PixelRenderer +type Canvas struct { // the worker in which our WASM application is running worker js.Value @@ -28,34 +28,34 @@ type CanvasTV struct { image []byte } -// NewCanvasTV is the preferred method of initialisation for the CanvasTV type -func NewCanvasTV(worker js.Value) *CanvasTV { +// NewCanvas is the preferred method of initialisation for the Canvas type +func NewCanvas(worker js.Value) *Canvas { var err error - scr := CanvasTV{worker: worker} + scr := &Canvas{worker: worker} - scr.Television, err = television.NewStellaTelevision("NTSC") + scr.Television, err = television.NewTelevision("NTSC") if err != nil { return nil } - scr.Television.AddPixelRenderer(&scr) + scr.Television.AddPixelRenderer(scr) // change tv spec after window creation (so we can set the window size) - err = scr.Resize(scr.GetSpec().ScanlineTop, scr.GetSpec().ScanlinesPerVisible) + err = scr.Resize(scr.GetSpec().ScanlineTop, scr.GetSpec().ScanlinesVisible) if err != nil { return nil } - return &scr + return scr } -func (scr *CanvasTV) Resize(topScanline, numScanlines int) error { +func (scr *Canvas) Resize(topScanline, numScanlines int) error { scr.top = topScanline scr.height = numScanlines * vertScale // strictly, only the height will ever change on a specification change but // it's convenient to set the width too - scr.width = television.ClocksPerVisible * pixelWidth * horizScale + scr.width = television.HorizClksVisible * pixelWidth * horizScale // recreate image buffer of correct length scr.image = make([]byte, scr.width*scr.height*pixelDepth) @@ -67,7 +67,7 @@ func (scr *CanvasTV) Resize(topScanline, numScanlines int) error { } // NewFrame implements telvision.PixelRenderer -func (scr *CanvasTV) NewFrame(frameNum int) error { +func (scr *Canvas) NewFrame(frameNum int) error { scr.worker.Call("updateDebug", "frameNum", frameNum) encodedImage := base64.StdEncoding.EncodeToString(scr.image) scr.worker.Call("updateCanvas", encodedImage) @@ -79,13 +79,13 @@ func (scr *CanvasTV) NewFrame(frameNum int) error { } // NewScanline implements telvision.PixelRenderer -func (scr *CanvasTV) NewScanline(scanline int) error { +func (scr *Canvas) NewScanline(scanline int) error { scr.worker.Call("updateDebug", "scanline", scanline) return nil } // SetPixel implements telvision.PixelRenderer -func (scr *CanvasTV) SetPixel(x, y int, red, green, blue byte, vblank bool) error { +func (scr *Canvas) SetPixel(x, y int, red, green, blue byte, vblank bool) error { if vblank { // we could return immediately but if vblank is on inside the visible // area we need to the set pixel to black, in case the vblank was off @@ -97,7 +97,7 @@ func (scr *CanvasTV) SetPixel(x, y int, red, green, blue byte, vblank bool) erro } // adjust pixels so we're only dealing with the visible range - x -= television.ClocksPerHblank + x -= television.HorizClksHBlank y -= scr.top if x < 0 || y < 0 { @@ -122,6 +122,6 @@ func (scr *CanvasTV) SetPixel(x, y int, red, green, blue byte, vblank bool) erro } // SetAltPixel implements telvision.PixelRenderer -func (scr *CanvasTV) SetAltPixel(x, y int, red, green, blue byte, vblank bool) error { +func (scr *Canvas) SetAltPixel(x, y int, red, green, blue byte, vblank bool) error { return nil } diff --git a/web2600/src/web2600.go b/web2600/src/web2600.go index 175917a8..60380aa7 100644 --- a/web2600/src/web2600.go +++ b/web2600/src/web2600.go @@ -12,10 +12,10 @@ import ( func main() { worker := js.Global().Get("self") - ctv := NewCanvasTV(worker) + scr := NewCanvas(worker) // create new vcs - vcs, err := hardware.NewVCS(ctv) + vcs, err := hardware.NewVCS(scr) if err != nil { panic(err) }