mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
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
This commit is contained in:
parent
cc8c14f0ae
commit
8ad481e132
40 changed files with 917 additions and 937 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ const (
|
|||
|
||||
// tv
|
||||
UnknownTVRequest
|
||||
StellaTelevision
|
||||
Television
|
||||
|
||||
// screen digest
|
||||
ScreenDigest
|
||||
|
|
|
@ -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)",
|
||||
|
|
|
@ -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
|
||||
|
|
53
gui/events.go
Normal file
53
gui/events.go
Normal file
|
@ -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
|
||||
}
|
78
gui/gui.go
78
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
|
||||
}
|
||||
|
|
1
gui/headless.go
Normal file
1
gui/headless.go
Normal file
|
@ -0,0 +1 @@
|
|||
package gui
|
24
gui/requests.go
Normal file
24
gui/requests.go
Normal file
|
@ -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
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -15,7 +15,7 @@ const (
|
|||
|
||||
type peripheral struct {
|
||||
id PeriphID
|
||||
handle func(Event) error
|
||||
handle func(Action) error
|
||||
|
||||
controller Controller
|
||||
prevController Controller
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
155
television/protocol.go
Normal file
155
television/protocol.go
Normal file
|
@ -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
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue