mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
o performance
- added performance package - moved fps() function from gopher.go to new package - added percentage of full-speed indicator to fps o gopher2600 - changed FPS mode to PERFORMANCE o polycounter - squeezed a little more time out of polycounter by changing the receivers of some frequently called functions to pointer receivers. o television / colors - changed how colors are transalted from signals - the color specifications are pre-processed in an init() function
This commit is contained in:
parent
6272b90892
commit
1073c53724
13 changed files with 260 additions and 426 deletions
128
gopher2600.go
128
gopher2600.go
|
@ -8,21 +8,14 @@ import (
|
|||
"gopher2600/debugger/console"
|
||||
"gopher2600/disassembly"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/gui"
|
||||
"gopher2600/gui/sdl"
|
||||
"gopher2600/hardware"
|
||||
"gopher2600/performance"
|
||||
"gopher2600/playmode"
|
||||
"gopher2600/recorder"
|
||||
"gopher2600/regression"
|
||||
"gopher2600/television"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultInitScript = ".gopher2600/debuggerInit"
|
||||
|
@ -35,12 +28,12 @@ func main() {
|
|||
var modeFlags *flag.FlagSet
|
||||
var modeFlagsParse func()
|
||||
|
||||
progModes := []string{"RUN", "PLAY", "DEBUG", "DISASM", "FPS", "REGRESS"}
|
||||
progModes := []string{"RUN", "PLAY", "DEBUG", "DISASM", "PERFORMANCE", "REGRESS"}
|
||||
defaultMode := "RUN"
|
||||
|
||||
progFlags := flag.NewFlagSet(progName, flag.ContinueOnError)
|
||||
|
||||
// prevent Parse() from outputting it's own error messages
|
||||
// prevent Parse() (part of the flag package) from outputting it's own error messages
|
||||
progFlags.SetOutput(&nopWriter{})
|
||||
|
||||
err := progFlags.Parse(os.Args[1:])
|
||||
|
@ -205,7 +198,7 @@ func main() {
|
|||
os.Exit(2)
|
||||
}
|
||||
|
||||
case "FPS":
|
||||
case "PERFORMANCE":
|
||||
display := modeFlags.Bool("display", false, "display TV output: boolean")
|
||||
tvType := modeFlags.String("tv", "NTSC", "television specification: NTSC, PAL")
|
||||
scaling := modeFlags.Float64("scale", 3.0, "television scaling")
|
||||
|
@ -218,7 +211,7 @@ func main() {
|
|||
fmt.Println("* 2600 cartridge required")
|
||||
os.Exit(2)
|
||||
case 1:
|
||||
err := fps(*profile, modeFlags.Arg(0), *display, *tvType, float32(*scaling), *runTime)
|
||||
err := performance.Check(os.Stdout, *profile, modeFlags.Arg(0), *display, *tvType, float32(*scaling), *runTime)
|
||||
if err != nil {
|
||||
fmt.Printf("* %s\n", err)
|
||||
os.Exit(2)
|
||||
|
@ -336,117 +329,6 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func fps(profile bool, cartridgeFile string, display bool, tvType string, scaling float32, runTime string) error {
|
||||
var fpstv television.Television
|
||||
var err error
|
||||
|
||||
if display {
|
||||
fpstv, err = sdl.NewGUI(tvType, scaling, nil)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
err = fpstv.(gui.GUI).SetFeature(gui.ReqSetVisibility, true)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
} else {
|
||||
fpstv, err = television.NewBasicTelevision("NTSC")
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
}
|
||||
|
||||
vcs, err := hardware.NewVCS(fpstv)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
err = vcs.AttachCartridge(cartridgeFile)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// write cpu profile
|
||||
if profile {
|
||||
f, err := os.Create("cpu.profile")
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
err = pprof.StartCPUProfile(f)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// get starting frame number
|
||||
fn, err := fpstv.GetState(television.ReqFramenum)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
startFrame := fn
|
||||
|
||||
// run for specified period of time
|
||||
|
||||
// -- parse supplied duration
|
||||
duration, err := time.ParseDuration(runTime)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// -- setup trigger that expires when duration has elapsed
|
||||
var timerRunning atomic.Value
|
||||
timerRunning.Store(1)
|
||||
|
||||
go func() {
|
||||
// force a two second leadtime to allow framerate to settle down
|
||||
time.AfterFunc(2*time.Second, func() {
|
||||
fn, _ = fpstv.GetState(television.ReqFramenum)
|
||||
startFrame = fn
|
||||
time.AfterFunc(duration, func() {
|
||||
timerRunning.Store(-1)
|
||||
})
|
||||
})
|
||||
}()
|
||||
|
||||
// -- run until specified time elapses (running is changed to -1)
|
||||
err = vcs.Run(func() (bool, error) {
|
||||
return timerRunning.Load().(int) > 0, nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// get ending frame number
|
||||
fn, err = vcs.TV.GetState(television.ReqFramenum)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
endFrame := fn
|
||||
|
||||
// calculate and display frames-per-second
|
||||
frameCount := endFrame - startFrame
|
||||
fps := float64(frameCount) / duration.Seconds()
|
||||
fmt.Printf("%.2f fps (%d frames in %.2f seconds)\n", fps, frameCount, duration.Seconds())
|
||||
|
||||
// write memory profile
|
||||
if profile {
|
||||
f, err := os.Create("mem.profile")
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
runtime.GC()
|
||||
err = pprof.WriteHeapProfile(f)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// special purpose io.Reader / io.Writer
|
||||
|
||||
type nopWriter struct{}
|
||||
|
|
|
@ -31,7 +31,7 @@ type screen struct {
|
|||
|
||||
// altPixels mirrors the pixels array with alternative color palette
|
||||
// -- useful for switching between regular and debug colors
|
||||
// -- allocated but only used if tv.allowDebugging and useAltPixels is true
|
||||
// -- allocated but only used if gtv.allowDebugging and useAltPixels is true
|
||||
altPixels []byte
|
||||
altPixelsFade []byte
|
||||
useAltPixels bool
|
||||
|
@ -208,7 +208,8 @@ func (scr *screen) setPixel(pixels *[]byte, x, y int32, red, green, blue byte, v
|
|||
scr.lastX = x
|
||||
scr.lastY = y
|
||||
|
||||
// do not plot pixel if VBLANK is on. some ROMs use VBLANK to output black
|
||||
// do not plot pixel if VBLANK is on. some ROMs use VBLANK to output black,
|
||||
// rather than having to play around with the current color of the sprites
|
||||
//
|
||||
// ROMs affected:
|
||||
// * Custer's Revenge
|
||||
|
|
|
@ -44,11 +44,13 @@ type CPU struct {
|
|||
// silently ignore addressing errors unless StrictAddressing is true
|
||||
StrictAddressing bool
|
||||
|
||||
// NoFlowControl sets whehter the cpu responds accurately to instructions that
|
||||
// affect the flow of the program (branches, JPS, subroutines and interrupts).
|
||||
// we use this in the disassembly package to make sure we reach every part of
|
||||
// the program. note that the alteration flow as a result of bank switching is
|
||||
// still possible.
|
||||
// NoFlowControl sets whehter the cpu responds accurately to instructions
|
||||
// that affect the flow of the program (branches, JPS, subroutines and
|
||||
// interrupts). we use this in the disassembly package to make sure we
|
||||
// reach every part of the program.
|
||||
//
|
||||
// note that the alteration of flow as a result of bank switching is still
|
||||
// possible even if NoFlowControl is true
|
||||
NoFlowControl bool
|
||||
}
|
||||
|
||||
|
@ -202,9 +204,15 @@ func (mc *CPU) read8Bit(address uint16) (uint8, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return val, mc.endCycle()
|
||||
err = mc.endCycle()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// note that read16Bit calls endCycle as appropriate
|
||||
func (mc *CPU) read16Bit(address uint16) (uint16, error) {
|
||||
lo, err := mc.mem.Read(address)
|
||||
if err != nil {
|
||||
|
|
|
@ -110,36 +110,36 @@ func (pk *Polycounter) Tick() bool {
|
|||
}
|
||||
|
||||
// Match check whether polycounter is at the given count, any phase
|
||||
func (pk Polycounter) Match(count int) bool {
|
||||
func (pk *Polycounter) Match(count int) bool {
|
||||
return pk.Count == count
|
||||
}
|
||||
|
||||
// MatchPhase checks whether polycounter is at the given count and given phase
|
||||
func (pk Polycounter) MatchPhase(count, phase int) bool {
|
||||
func (pk *Polycounter) MatchPhase(count, phase int) bool {
|
||||
return pk.Count == count && pk.Phase == phase
|
||||
}
|
||||
|
||||
// MatchEnd checks whether polycounter is at the *end* (ie. last phase) of the given count
|
||||
func (pk Polycounter) MatchEnd(count int) bool {
|
||||
func (pk *Polycounter) MatchEnd(count int) bool {
|
||||
return pk.Count == count && pk.Phase == MaxPhase
|
||||
}
|
||||
|
||||
// MatchBeginning checks whether polycounter is at the *beginning* (ie. first phase) of the given count
|
||||
func (pk Polycounter) MatchBeginning(count int) bool {
|
||||
func (pk *Polycounter) MatchBeginning(count int) bool {
|
||||
return pk.Count == count && pk.Phase == 0
|
||||
}
|
||||
|
||||
// Pixel returns the color clock when expressed as a pixel
|
||||
func (pk Polycounter) Pixel() int {
|
||||
func (pk *Polycounter) Pixel() int {
|
||||
return (pk.Count * 4) + pk.Phase - 68
|
||||
}
|
||||
|
||||
// CycleOnNextTick checks to see if polycounter is about to cycle
|
||||
func (pk Polycounter) CycleOnNextTick() bool {
|
||||
func (pk *Polycounter) CycleOnNextTick() bool {
|
||||
return pk.Count == pk.ResetPoint && pk.Phase == MaxPhase
|
||||
}
|
||||
|
||||
// CycledOnLastTick checks to see if polycounter has just cycled
|
||||
func (pk Polycounter) CycledOnLastTick() bool {
|
||||
func (pk *Polycounter) CycledOnLastTick() bool {
|
||||
return pk.Count == 0 && pk.Phase == 0
|
||||
}
|
||||
|
|
115
performance/fps.go
Normal file
115
performance/fps.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package performance
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/gui"
|
||||
"gopher2600/gui/sdl"
|
||||
"gopher2600/hardware"
|
||||
"gopher2600/television"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
fps = float64(numFrames) / duration
|
||||
accuracy = 100 * float64(numFrames) / (duration * ftv.GetSpec().FramesPerSecond)
|
||||
return fps, accuracy
|
||||
}
|
||||
|
||||
// Check is a very rough and ready calculation of the emulator's performance
|
||||
func Check(output io.Writer, profile bool, cartridgeFile string, display bool, tvType string, scaling float32, runTime string) error {
|
||||
var ftv television.Television
|
||||
var err error
|
||||
|
||||
// create the "correct" type of TV depending on whether the display flag is
|
||||
// set or not
|
||||
if display {
|
||||
ftv, err = sdl.NewGUI(tvType, scaling, nil)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
err = ftv.(gui.GUI).SetFeature(gui.ReqSetVisibility, true)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
} else {
|
||||
ftv, err = television.NewBasicTelevision(tvType)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
}
|
||||
|
||||
// create vcs using the tv created above
|
||||
vcs, err := hardware.NewVCS(ftv)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// attach cartridge to te vcs
|
||||
err = vcs.AttachCartridge(cartridgeFile)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// parse supplied duration
|
||||
duration, err := time.ParseDuration(runTime)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// get starting frame number (should be 0)
|
||||
startFrame, err := ftv.GetState(television.ReqFramenum)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// run for specified period of time
|
||||
err = cpuProfile(profile, "cpu.profile", func() error {
|
||||
// setup trigger that expires when duration has elapsed
|
||||
timesUp := make(chan bool)
|
||||
|
||||
// force a two second leadtime to allow framerate to settle down and
|
||||
// then restart timer for the specified duration
|
||||
go func() {
|
||||
time.AfterFunc(2*time.Second, func() {
|
||||
startFrame, _ = ftv.GetState(television.ReqFramenum)
|
||||
time.AfterFunc(duration, func() {
|
||||
timesUp <- true
|
||||
})
|
||||
})
|
||||
}()
|
||||
|
||||
// run until specified time elapses
|
||||
err = vcs.Run(func() (bool, error) {
|
||||
select {
|
||||
case v := <-timesUp:
|
||||
return !v, nil
|
||||
default:
|
||||
return true, nil
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
// get ending frame number
|
||||
endFrame, err := vcs.TV.GetState(television.ReqFramenum)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
|
||||
numFrames := endFrame - startFrame
|
||||
fps, accuracy := CalcFPS(ftv, numFrames, duration.Seconds())
|
||||
output.Write([]byte(fmt.Sprintf("%.2f fps (%d frames in %.2f seconds) %.1f%%\n", fps, numFrames, duration.Seconds(), accuracy)))
|
||||
|
||||
return memProfile(profile, "mem.profile")
|
||||
}
|
42
performance/profiling.go
Normal file
42
performance/profiling.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package performance
|
||||
|
||||
import (
|
||||
"gopher2600/errors"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
)
|
||||
|
||||
func cpuProfile(profile bool, outFile string, run func() error) error {
|
||||
if profile {
|
||||
// write cpu profile
|
||||
f, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
err = pprof.StartCPUProfile(f)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
return run()
|
||||
}
|
||||
|
||||
func memProfile(profile bool, outFile string) error {
|
||||
if profile {
|
||||
f, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
runtime.GC()
|
||||
err = pprof.WriteHeapProfile(f)
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.FPSError, err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -95,26 +95,26 @@ func NewPlayback(transcript string) (*Playback, error) {
|
|||
|
||||
n, err := strconv.Atoi(toks[fieldEvent])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:2], fieldSep)))
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldEvent+1], fieldSep)))
|
||||
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
|
||||
}
|
||||
event.event = peripherals.Event(n)
|
||||
|
||||
event.frame, err = strconv.Atoi(toks[fieldFrame])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:3], fieldSep)))
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldFrame+1], fieldSep)))
|
||||
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
|
||||
}
|
||||
|
||||
event.scanline, err = strconv.Atoi(toks[fieldScanline])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:4], fieldSep)))
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldScanline+1], fieldSep)))
|
||||
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
|
||||
}
|
||||
|
||||
event.horizpos, err = strconv.Atoi(toks[fieldHorizPos])
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:5], fieldSep)))
|
||||
msg := fmt.Sprintf("%s line %d, col %d", err, i+1, len(strings.Join(toks[:fieldHorizPos+1], fieldSep)))
|
||||
return nil, errors.NewFormattedError(errors.PlaybackError, msg)
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,10 @@ const (
|
|||
numFrameFields
|
||||
)
|
||||
|
||||
// FrameRegression is the simplest regression type
|
||||
// FrameRegression is the simplest regression type. it works by running the
|
||||
// emulation for N frames and the screen digest recorded at that point.
|
||||
// regression tests pass if the screen digest after N frames matches the stored
|
||||
// value.
|
||||
type FrameRegression struct {
|
||||
key int
|
||||
CartFile string
|
||||
|
@ -102,4 +105,5 @@ func (reg *FrameRegression) regress(newRegression bool) (bool, error) {
|
|||
}
|
||||
|
||||
func (reg FrameRegression) cleanUp() {
|
||||
// no cleanup required for frame regression type
|
||||
}
|
||||
|
|
|
@ -19,8 +19,10 @@ const (
|
|||
numPlaybackFields
|
||||
)
|
||||
|
||||
// PlaybackRegression represents a regression type that processes vcs playback
|
||||
// recording
|
||||
// PlaybackRegression represents a regression type that processes a VCS
|
||||
// recording. playback regressions can take a while to run because by their
|
||||
// nature they extend over many frames - many more than is typical with the
|
||||
// FrameRegression type.
|
||||
type PlaybackRegression struct {
|
||||
key int
|
||||
Script string
|
||||
|
@ -163,5 +165,6 @@ func (reg *PlaybackRegression) regress(newRegression bool) (bool, error) {
|
|||
}
|
||||
|
||||
func (reg PlaybackRegression) cleanUp() {
|
||||
// ignore errors from remove process
|
||||
_ = os.Remove(reg.Script)
|
||||
}
|
||||
|
|
|
@ -14,9 +14,6 @@ type Handler interface {
|
|||
// the database
|
||||
getID() string
|
||||
|
||||
// String implements the Stringer interface
|
||||
String() string
|
||||
|
||||
// setKey sets the key value for the regression
|
||||
setKey(int)
|
||||
|
||||
|
@ -34,6 +31,9 @@ type Handler interface {
|
|||
// action perfomed when regression entry is removed from database. for
|
||||
// example, removing additional files from disk
|
||||
cleanUp()
|
||||
|
||||
// String implements the Stringer interface
|
||||
String() string
|
||||
}
|
||||
|
||||
// RegressList displays all entries in the database
|
||||
|
|
|
@ -227,7 +227,7 @@ func (btv *BasicTelevision) Signal(sig SignalAttributes) error {
|
|||
y := int32(btv.scanline)
|
||||
|
||||
// decode color using the regular color signal
|
||||
red, green, blue := btv.spec.TranslateColorSignal(sig.Pixel)
|
||||
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 {
|
||||
|
@ -236,7 +236,7 @@ func (btv *BasicTelevision) Signal(sig SignalAttributes) error {
|
|||
}
|
||||
|
||||
// decode color using the alternative color signal
|
||||
red, green, blue = btv.spec.TranslateColorSignal(sig.AltPixel)
|
||||
red, green, blue = getColor(btv.spec, sig.AltPixel)
|
||||
for f := range btv.renderers {
|
||||
err := btv.renderers[f].SetAltPixel(x, y, red, green, blue, sig.VBlank)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,270 +5,53 @@ package television
|
|||
type ColorSignal uint16
|
||||
|
||||
// VideoBlack is the PixelSignal value that indicates no VCS pixel is to be
|
||||
// shown
|
||||
// shown - the need for the video black signal explains why ColorSignal is
|
||||
// defined to be uint16 and not uint8, as you might expect
|
||||
const VideoBlack ColorSignal = 0xffff
|
||||
|
||||
// color is the color that is show on screen. different television
|
||||
// specifications interpret colors differently
|
||||
type color uint32
|
||||
// components of the translated color signal
|
||||
const (
|
||||
red = iota
|
||||
green
|
||||
blue
|
||||
colorDepth
|
||||
)
|
||||
|
||||
// TODO: implement PAL colors
|
||||
// a color is made of a number of color components
|
||||
type color [colorDepth]byte
|
||||
|
||||
// ntscColors is the list of NTSC colors for each value of possible pixel
|
||||
var ntscColors = []color{
|
||||
0x000000,
|
||||
0x000000,
|
||||
0x404040,
|
||||
0x404040,
|
||||
0x6c6c6c,
|
||||
0x6c6c6c,
|
||||
0x909090,
|
||||
0x909090,
|
||||
0xb0b0b0,
|
||||
0xb0b0b0,
|
||||
0xc8c8c8,
|
||||
0xc8c8c8,
|
||||
0xdcdcdc,
|
||||
0xdcdcdc,
|
||||
0xececec,
|
||||
0xececec,
|
||||
0x444400,
|
||||
0x444400,
|
||||
0x646410,
|
||||
0x646410,
|
||||
0x848424,
|
||||
0x848424,
|
||||
0xa0a034,
|
||||
0xa0a034,
|
||||
0xb8b840,
|
||||
0xb8b840,
|
||||
0xd0d050,
|
||||
0xd0d050,
|
||||
0xe8e85c,
|
||||
0xe8e85c,
|
||||
0xfcfc68,
|
||||
0xfcfc68,
|
||||
0x702800,
|
||||
0x702800,
|
||||
0x844414,
|
||||
0x844414,
|
||||
0x985c28,
|
||||
0x985c28,
|
||||
0xac783c,
|
||||
0xac783c,
|
||||
0xbc8c4c,
|
||||
0xbc8c4c,
|
||||
0xcca05c,
|
||||
0xcca05c,
|
||||
0xdcb468,
|
||||
0xdcb468,
|
||||
0xecc878,
|
||||
0xecc878,
|
||||
0x841800,
|
||||
0x841800,
|
||||
0x983418,
|
||||
0x983418,
|
||||
0xac5030,
|
||||
0xac5030,
|
||||
0xc06848,
|
||||
0xc06848,
|
||||
0xd0805c,
|
||||
0xd0805c,
|
||||
0xe09470,
|
||||
0xe09470,
|
||||
0xeca880,
|
||||
0xeca880,
|
||||
0xfcbc94,
|
||||
0xfcbc94,
|
||||
0x880000,
|
||||
0x880000,
|
||||
0x9c2020,
|
||||
0x9c2020,
|
||||
0xb03c3c,
|
||||
0xb03c3c,
|
||||
0xc05858,
|
||||
0xc05858,
|
||||
0xd07070,
|
||||
0xd07070,
|
||||
0xe08888,
|
||||
0xe08888,
|
||||
0xeca0a0,
|
||||
0xeca0a0,
|
||||
0xfcb4b4,
|
||||
0xfcb4b4,
|
||||
0x78005c,
|
||||
0x78005c,
|
||||
0x8c2074,
|
||||
0x8c2074,
|
||||
0xa03c88,
|
||||
0xa03c88,
|
||||
0xb0589c,
|
||||
0xb0589c,
|
||||
0xc070b0,
|
||||
0xc070b0,
|
||||
0xd084c0,
|
||||
0xd084c0,
|
||||
0xdc9cd0,
|
||||
0xdc9cd0,
|
||||
0xecb0e0,
|
||||
0xecb0e0,
|
||||
0x480078,
|
||||
0x480078,
|
||||
0x602090,
|
||||
0x602090,
|
||||
0x783ca4,
|
||||
0x783ca4,
|
||||
0x8c58b8,
|
||||
0x8c58b8,
|
||||
0xa070cc,
|
||||
0xa070cc,
|
||||
0xb484dc,
|
||||
0xb484dc,
|
||||
0xc49cec,
|
||||
0xc49cec,
|
||||
0xd4b0fc,
|
||||
0xd4b0fc,
|
||||
0x140084,
|
||||
0x140084,
|
||||
0x302098,
|
||||
0x302098,
|
||||
0x4c3cac,
|
||||
0x4c3cac,
|
||||
0x6858c0,
|
||||
0x6858c0,
|
||||
0x7c70d0,
|
||||
0x7c70d0,
|
||||
0x9488e0,
|
||||
0x9488e0,
|
||||
0xa8a0ec,
|
||||
0xa8a0ec,
|
||||
0xbcb4fc,
|
||||
0xbcb4fc,
|
||||
0x000088,
|
||||
0x000088,
|
||||
0x1c209c,
|
||||
0x1c209c,
|
||||
0x3840b0,
|
||||
0x3840b0,
|
||||
0x505cc0,
|
||||
0x505cc0,
|
||||
0x6874d0,
|
||||
0x6874d0,
|
||||
0x7c8ce0,
|
||||
0x7c8ce0,
|
||||
0x90a4ec,
|
||||
0x90a4ec,
|
||||
0xa4b8fc,
|
||||
0xa4b8fc,
|
||||
0x00187c,
|
||||
0x00187c,
|
||||
0x1c3890,
|
||||
0x1c3890,
|
||||
0x3854a8,
|
||||
0x3854a8,
|
||||
0x5070bc,
|
||||
0x5070bc,
|
||||
0x6888cc,
|
||||
0x6888cc,
|
||||
0x7c9cdc,
|
||||
0x7c9cdc,
|
||||
0x90b4ec,
|
||||
0x90b4ec,
|
||||
0xa4c8fc,
|
||||
0xa4c8fc,
|
||||
0x002c5c,
|
||||
0x002c5c,
|
||||
0x1c4c78,
|
||||
0x1c4c78,
|
||||
0x386890,
|
||||
0x386890,
|
||||
0x5084ac,
|
||||
0x5084ac,
|
||||
0x689cc0,
|
||||
0x689cc0,
|
||||
0x7cb4d4,
|
||||
0x7cb4d4,
|
||||
0x90cce8,
|
||||
0x90cce8,
|
||||
0xa4e0fc,
|
||||
0xa4e0fc,
|
||||
0x003c2c,
|
||||
0x003c2c,
|
||||
0x1c5c48,
|
||||
0x1c5c48,
|
||||
0x387c64,
|
||||
0x387c64,
|
||||
0x509c80,
|
||||
0x509c80,
|
||||
0x68b494,
|
||||
0x68b494,
|
||||
0x7cd0ac,
|
||||
0x7cd0ac,
|
||||
0x90e4c0,
|
||||
0x90e4c0,
|
||||
0xa4fcd4,
|
||||
0xa4fcd4,
|
||||
0x003c00,
|
||||
0x003c00,
|
||||
0x205c20,
|
||||
0x205c20,
|
||||
0x407c40,
|
||||
0x407c40,
|
||||
0x5c9c5c,
|
||||
0x5c9c5c,
|
||||
0x74b474,
|
||||
0x74b474,
|
||||
0x8cd08c,
|
||||
0x8cd08c,
|
||||
0xa4e4a4,
|
||||
0xa4e4a4,
|
||||
0xb8fcb8,
|
||||
0xb8fcb8,
|
||||
0x143800,
|
||||
0x143800,
|
||||
0x345c1c,
|
||||
0x345c1c,
|
||||
0x507c38,
|
||||
0x507c38,
|
||||
0x6c9850,
|
||||
0x6c9850,
|
||||
0x84b468,
|
||||
0x84b468,
|
||||
0x9ccc7c,
|
||||
0x9ccc7c,
|
||||
0xb4e490,
|
||||
0xb4e490,
|
||||
0xc8fca4,
|
||||
0xc8fca4,
|
||||
0x2c3000,
|
||||
0x2c3000,
|
||||
0x4c501c,
|
||||
0x4c501c,
|
||||
0x687034,
|
||||
0x687034,
|
||||
0x848c4c,
|
||||
0x848c4c,
|
||||
0x9ca864,
|
||||
0x9ca864,
|
||||
0xb4c078,
|
||||
0xb4c078,
|
||||
0xccd488,
|
||||
0xccd488,
|
||||
0xe0ec9c,
|
||||
0xe0ec9c,
|
||||
0x442800,
|
||||
0x442800,
|
||||
0x644818,
|
||||
0x644818,
|
||||
0x846830,
|
||||
0x846830,
|
||||
0xa08444,
|
||||
0xa08444,
|
||||
0xb89c58,
|
||||
0xb89c58,
|
||||
0xd0b46c,
|
||||
0xd0b46c,
|
||||
0xe8cc7c,
|
||||
0xe8cc7c,
|
||||
0xfce08c,
|
||||
0xfce08c}
|
||||
// colors is the entire palette
|
||||
type colors []color
|
||||
|
||||
// the entire palette is made up of many colors
|
||||
var colorsNTSC = colors{}
|
||||
var colorsPAL = colors{}
|
||||
|
||||
// the VideoBlack signal results in the following color
|
||||
var videoBlack = color{0, 0, 0}
|
||||
|
||||
// the raw color values are the component values expressed as a single 32 bit
|
||||
// number. we'll use these raw values in the init() function below to create
|
||||
// the real palette
|
||||
var colorsNTSCRaw = []uint32{
|
||||
0x000000, 0x000000, 0x404040, 0x404040, 0x6c6c6c, 0x6c6c6c, 0x909090, 0x909090, 0xb0b0b0, 0xb0b0b0, 0xc8c8c8, 0xc8c8c8, 0xdcdcdc, 0xdcdcdc, 0xececec, 0xececec, 0x444400, 0x444400, 0x646410, 0x646410, 0x848424, 0x848424, 0xa0a034, 0xa0a034, 0xb8b840, 0xb8b840, 0xd0d050, 0xd0d050, 0xe8e85c, 0xe8e85c, 0xfcfc68, 0xfcfc68, 0x702800, 0x702800, 0x844414, 0x844414, 0x985c28, 0x985c28, 0xac783c, 0xac783c, 0xbc8c4c, 0xbc8c4c, 0xcca05c, 0xcca05c, 0xdcb468, 0xdcb468, 0xecc878, 0xecc878, 0x841800, 0x841800, 0x983418, 0x983418, 0xac5030, 0xac5030, 0xc06848, 0xc06848, 0xd0805c, 0xd0805c, 0xe09470, 0xe09470, 0xeca880, 0xeca880, 0xfcbc94, 0xfcbc94, 0x880000, 0x880000, 0x9c2020, 0x9c2020, 0xb03c3c, 0xb03c3c, 0xc05858, 0xc05858, 0xd07070, 0xd07070, 0xe08888, 0xe08888, 0xeca0a0, 0xeca0a0, 0xfcb4b4, 0xfcb4b4, 0x78005c, 0x78005c, 0x8c2074, 0x8c2074, 0xa03c88, 0xa03c88, 0xb0589c, 0xb0589c, 0xc070b0, 0xc070b0, 0xd084c0, 0xd084c0, 0xdc9cd0, 0xdc9cd0, 0xecb0e0, 0xecb0e0, 0x480078, 0x480078, 0x602090, 0x602090, 0x783ca4, 0x783ca4, 0x8c58b8, 0x8c58b8, 0xa070cc, 0xa070cc, 0xb484dc, 0xb484dc, 0xc49cec, 0xc49cec, 0xd4b0fc, 0xd4b0fc, 0x140084, 0x140084, 0x302098, 0x302098, 0x4c3cac, 0x4c3cac, 0x6858c0, 0x6858c0, 0x7c70d0, 0x7c70d0, 0x9488e0, 0x9488e0, 0xa8a0ec, 0xa8a0ec, 0xbcb4fc, 0xbcb4fc, 0x000088, 0x000088, 0x1c209c, 0x1c209c, 0x3840b0, 0x3840b0, 0x505cc0, 0x505cc0, 0x6874d0, 0x6874d0, 0x7c8ce0, 0x7c8ce0, 0x90a4ec, 0x90a4ec, 0xa4b8fc, 0xa4b8fc, 0x00187c, 0x00187c, 0x1c3890, 0x1c3890, 0x3854a8, 0x3854a8, 0x5070bc, 0x5070bc, 0x6888cc, 0x6888cc, 0x7c9cdc, 0x7c9cdc, 0x90b4ec, 0x90b4ec, 0xa4c8fc, 0xa4c8fc, 0x002c5c, 0x002c5c, 0x1c4c78, 0x1c4c78, 0x386890, 0x386890, 0x5084ac, 0x5084ac, 0x689cc0, 0x689cc0, 0x7cb4d4, 0x7cb4d4, 0x90cce8, 0x90cce8, 0xa4e0fc, 0xa4e0fc, 0x003c2c, 0x003c2c, 0x1c5c48, 0x1c5c48, 0x387c64, 0x387c64, 0x509c80, 0x509c80, 0x68b494, 0x68b494, 0x7cd0ac, 0x7cd0ac, 0x90e4c0, 0x90e4c0, 0xa4fcd4, 0xa4fcd4, 0x003c00, 0x003c00, 0x205c20, 0x205c20, 0x407c40, 0x407c40, 0x5c9c5c, 0x5c9c5c, 0x74b474, 0x74b474, 0x8cd08c, 0x8cd08c, 0xa4e4a4, 0xa4e4a4, 0xb8fcb8, 0xb8fcb8, 0x143800, 0x143800, 0x345c1c, 0x345c1c, 0x507c38, 0x507c38, 0x6c9850, 0x6c9850, 0x84b468, 0x84b468, 0x9ccc7c, 0x9ccc7c, 0xb4e490, 0xb4e490, 0xc8fca4, 0xc8fca4, 0x2c3000, 0x2c3000, 0x4c501c, 0x4c501c, 0x687034, 0x687034, 0x848c4c, 0x848c4c, 0x9ca864, 0x9ca864, 0xb4c078, 0xb4c078, 0xccd488, 0xccd488, 0xe0ec9c, 0xe0ec9c, 0x442800, 0x442800, 0x644818, 0x644818, 0x846830, 0x846830, 0xa08444, 0xa08444, 0xb89c58, 0xb89c58, 0xd0b46c, 0xd0b46c, 0xe8cc7c, 0xe8cc7c, 0xfce08c, 0xfce08c}
|
||||
|
||||
func init() {
|
||||
for i := range colorsNTSCRaw {
|
||||
col := colorsNTSCRaw[i]
|
||||
red, green, blue := byte((col&0xff0000)>>16), byte((col&0xff00)>>8), byte(col&0xff)
|
||||
colorsNTSC = append(colorsNTSC, color{red, green, blue})
|
||||
|
||||
// TODO: implement PAL colors correctly. just use NTSC palette for now
|
||||
colorsPAL = append(colorsPAL, color{red, green, blue})
|
||||
}
|
||||
}
|
||||
|
||||
// getColor translates a color signal to the individual color components
|
||||
func getColor(spec *Specification, sig ColorSignal) (byte, byte, byte) {
|
||||
if sig == VideoBlack {
|
||||
return videoBlack[red], videoBlack[green], videoBlack[blue]
|
||||
}
|
||||
col := spec.Colors[sig]
|
||||
return col[red], col[green], col[blue]
|
||||
}
|
||||
|
|
|
@ -16,22 +16,14 @@ type Specification struct {
|
|||
|
||||
VsyncClocks int
|
||||
|
||||
Colors []color
|
||||
Colors colors
|
||||
|
||||
IdealTop int
|
||||
IdealBottom int
|
||||
IdealScanlines int
|
||||
}
|
||||
|
||||
// TranslateColorSignal decodaes color signal to an RGB value
|
||||
func (spec Specification) TranslateColorSignal(sig ColorSignal) (byte, byte, byte) {
|
||||
red, green, blue := byte(0), byte(0), byte(0)
|
||||
if sig != VideoBlack {
|
||||
col := spec.Colors[sig]
|
||||
red, green, blue = byte((col&0xff0000)>>16), byte((col&0xff00)>>8), byte(col&0xff)
|
||||
}
|
||||
|
||||
return red, green, blue
|
||||
FramesPerSecond float64
|
||||
SecondsPerFrame float64
|
||||
}
|
||||
|
||||
// SpecNTSC is the specification for NTSC television typee
|
||||
|
@ -51,11 +43,13 @@ func init() {
|
|||
SpecNTSC.ScanlinesPerVisible = 192
|
||||
SpecNTSC.ScanlinesPerOverscan = 30
|
||||
SpecNTSC.ScanlinesTotal = 262
|
||||
SpecNTSC.Colors = ntscColors
|
||||
SpecNTSC.Colors = colorsNTSC
|
||||
SpecNTSC.VsyncClocks = SpecNTSC.ScanlinesPerVSync * SpecNTSC.ClocksPerScanline
|
||||
SpecNTSC.IdealTop = SpecNTSC.ScanlinesPerVSync + SpecNTSC.ScanlinesPerVBlank
|
||||
SpecNTSC.IdealBottom = SpecNTSC.ScanlinesTotal - SpecNTSC.ScanlinesPerOverscan
|
||||
SpecNTSC.IdealScanlines = SpecNTSC.IdealBottom - SpecNTSC.IdealTop
|
||||
SpecNTSC.FramesPerSecond = 60.0
|
||||
SpecNTSC.SecondsPerFrame = 1.0 / SpecNTSC.FramesPerSecond
|
||||
|
||||
SpecPAL = new(Specification)
|
||||
SpecPAL.ID = "PAL"
|
||||
|
@ -70,8 +64,10 @@ func init() {
|
|||
SpecPAL.IdealTop = SpecPAL.ScanlinesPerVSync + SpecPAL.ScanlinesPerVBlank
|
||||
SpecPAL.IdealBottom = SpecPAL.ScanlinesTotal - SpecPAL.ScanlinesPerOverscan
|
||||
SpecPAL.IdealScanlines = SpecPAL.IdealBottom - SpecPAL.IdealTop
|
||||
SpecPAL.FramesPerSecond = 50.0
|
||||
SpecPAL.SecondsPerFrame = 1.0 / SpecPAL.FramesPerSecond
|
||||
|
||||
// use NTSC colors for PAL specification for now
|
||||
// TODO: implement PAL colors
|
||||
SpecPAL.Colors = ntscColors
|
||||
SpecPAL.Colors = colorsNTSC
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue