From a16ca04b123a54fcd9a9ed8ee8a08ef13fd2a81d Mon Sep 17 00:00:00 2001 From: steve Date: Fri, 15 Jun 2018 08:34:37 +0100 Subject: [PATCH] o video - implemented trigger lists for missile/players - implemented player sprite --- debugger/commands.go | 6 ++- hardware/tia/video/missile.go | 3 +- hardware/tia/video/player.go | 78 ++++++++++++++++++++++++++++++++++- hardware/tia/video/sprite.go | 13 ++++-- hardware/tia/video/video.go | 50 +++++++++++++++------- hardware/vcs.go | 13 ++---- 6 files changed, 131 insertions(+), 32 deletions(-) diff --git a/debugger/commands.go b/debugger/commands.go index d5f87fee..3e990412 100644 --- a/debugger/commands.go +++ b/debugger/commands.go @@ -36,6 +36,8 @@ const ( SubKeywordBreaks = "BREAKS" SubKeywordTraps = "TRAPS" + SubKeywordVideo = "VIDEO" + SubKeywordCPU = "CPU" ) // DebuggerCommands provides: @@ -44,7 +46,7 @@ const ( var DebuggerCommands = parser.Commands{ KeywordInsert: parser.CommandArgs{parser.Arg{Typ: parser.ArgFile, Req: true}}, KeywordBreak: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgNumber, Req: true}}, - KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}, parser.Arg{Typ: parser.ArgNumber, Req: true}}, + KeywordTrap: parser.CommandArgs{parser.Arg{Typ: parser.ArgTarget, Req: true}}, KeywordOnHalt: parser.CommandArgs{parser.Arg{Typ: parser.ArgIndeterminate, Req: false}}, KeywordList: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}}, KeywordClear: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: true, Vals: parser.Keywords{SubKeywordBreaks, SubKeywordTraps}}}, @@ -53,7 +55,7 @@ var DebuggerCommands = parser.Commands{ KeywordReset: parser.CommandArgs{}, KeywordRun: parser.CommandArgs{}, KeywordStep: parser.CommandArgs{}, - KeywordStepMode: parser.CommandArgs{}, + KeywordStepMode: parser.CommandArgs{parser.Arg{Typ: parser.ArgKeyword, Req: false, Vals: parser.Keywords{SubKeywordCPU, SubKeywordVideo}}}, KeywordTerse: parser.CommandArgs{}, KeywordVerbose: parser.CommandArgs{}, KeywordVerbosity: parser.CommandArgs{}, diff --git a/hardware/tia/video/missile.go b/hardware/tia/video/missile.go index ea82f7ff..25681f46 100644 --- a/hardware/tia/video/missile.go +++ b/hardware/tia/video/missile.go @@ -12,6 +12,7 @@ type missileSprite struct { size uint8 enable bool futureEnable *future + triggerList []int lateStartDraw bool } @@ -61,7 +62,7 @@ func (ms missileSprite) MachineInfo() string { // tick moves the counters along for the missile sprite func (ms *missileSprite) tick() { // position - if ms.tickPosition(nil) { + if ms.tickPosition(ms.triggerList) { if ms.futureReset.isScheduled() { ms.stopDrawing() ms.lateStartDraw = true diff --git a/hardware/tia/video/player.go b/hardware/tia/video/player.go index f895d805..dfca62ea 100644 --- a/hardware/tia/video/player.go +++ b/hardware/tia/video/player.go @@ -1,7 +1,9 @@ package video import ( + "fmt" "gopher2600/hardware/tia/colorclock" + "math/bits" ) type playerSprite struct { @@ -10,8 +12,12 @@ type playerSprite struct { color uint8 gfxData uint8 gfxDataPrev uint8 - reflection bool + size uint8 + reflected bool verticalDelay bool + triggerList []int + + lateStartDraw bool } func newPlayerSprite(label string, colorClock *colorclock.ColorClock) *playerSprite { @@ -20,16 +26,83 @@ func newPlayerSprite(label string, colorClock *colorclock.ColorClock) *playerSpr return ps } +func (ps playerSprite) MachineInfoTerse() string { + gfxData := ps.gfxData + if ps.verticalDelay { + gfxData = ps.gfxDataPrev + } + ref := " " + if ps.reflected { + ref = "r" + } + return fmt.Sprintf("%s gfx: %s %08b", ps.sprite.MachineInfoTerse(), ref, gfxData) +} + // nothing to add to sprite type implementation of MachineInfo() and // MachineInfoTerse() // tick moves the counters along for the player sprite func (ps *playerSprite) tick() { + // position + if ps.tickPosition(ps.triggerList) { + if ps.futureReset.isScheduled() { + ps.stopDrawing() + ps.lateStartDraw = true + } else { + ps.startDrawing() + } + } else { + // if player.position.tick() has not caused the position counter to + // cycle then progress draw signal according to color clock phase and + // nusiz_player_width. for nusiz_player_width and 0b101 and 0b111, + // pixels are smeared over additional cycles in order to create the + // double and quadruple sized sprites + // + // NOTE: the key difference between player and missile ticking is the + // absence in the player sprite, of a filter on draw sig ticking when + // there a position reset is pending + if ps.size == 0x05 { + if ps.colorClock.Phase == 0 || ps.colorClock.Phase == 2 { + ps.tickDrawSig() + } + } else if ps.size == 0x07 { + if ps.colorClock.Phase == 2 { + ps.tickDrawSig() + } + } else { + ps.tickDrawSig() + } + } + + // reset + if ps.futureReset.tick() { + ps.resetPosition() + if ps.lateStartDraw { + ps.startDrawing() + ps.lateStartDraw = false + } + } } // pixel returns the color of the player at the current time. returns // (false, 0) if no pixel is to be seen; and (true, col) if there is func (ps *playerSprite) pixel() (bool, uint8) { + // vertical delay + gfxData := ps.gfxData + if ps.verticalDelay { + gfxData = ps.gfxDataPrev + } + + if ps.reflected { + gfxData = bits.Reverse8(gfxData) + } + + if ps.drawSigCount > 0 && ps.drawSigCount <= ps.drawSigMax { + if gfxData>>(uint8(ps.drawSigMax)-uint8(ps.drawSigCount))&0x01 == 0x01 { + return true, ps.color + } + } + return false, 0 } @@ -40,3 +113,6 @@ func (ps *playerSprite) scheduleReset(hblank *bool) { ps.futureReset.schedule(delayResetSprite, true) } } + +func (ps *playerSprite) scheduleReflection() { +} diff --git a/hardware/tia/video/sprite.go b/hardware/tia/video/sprite.go index bc745a4f..acfe7a28 100644 --- a/hardware/tia/video/sprite.go +++ b/hardware/tia/video/sprite.go @@ -25,15 +25,19 @@ type sprite struct { // position of the sprite as a polycounter value - the basic principle // behind VCS sprites is to begin drawing of the sprite when position // circulates to 0000000 - position polycounter.Polycounter + position polycounter.Polycounter + + // reset position of the sprite -- does not take horizonal movement into + // account positionResetPixel int // the draw signal controls which "bit" of the sprite is to be drawn next. // generally, the draw signal is activated when the position polycounter // matches the colorClock polycounter, but differenct sprite types handle // this differently in certain circumstances - drawSigMax int drawSigCount int + drawSigMax int + drawSigOff int } func newSprite(label string, colorClock *colorclock.ColorClock) *sprite { @@ -55,7 +59,8 @@ func newSprite(label string, colorClock *colorclock.ColorClock) *sprite { // the direction of count and max is important - don't monkey with it // the value is used in Pixel*() functions to determine which pixel to check sp.drawSigMax = 8 - sp.drawSigCount = sp.drawSigMax + 1 + sp.drawSigOff = sp.drawSigMax + 1 + sp.drawSigCount = sp.drawSigOff return sp } @@ -120,7 +125,7 @@ func (sp *sprite) startDrawing() { // stopDrawing is used to stop the draw signal prematurely func (sp *sprite) stopDrawing() { - sp.drawSigCount = sp.drawSigMax + 1 + sp.drawSigCount = sp.drawSigOff } func (sp *sprite) isDrawing() bool { diff --git a/hardware/tia/video/video.go b/hardware/tia/video/video.go index bbab95f7..51991ca4 100644 --- a/hardware/tia/video/video.go +++ b/hardware/tia/video/video.go @@ -25,8 +25,6 @@ type Video struct { hmm0 uint8 hmm1 uint8 hmbl uint8 - - // TODO: player/missile number & spacing; trigger lists } // New is the preferred method of initialisation for the Video structure @@ -115,10 +113,10 @@ func (vd *Video) TickSpritesForHMOVE(count int) { func (vd Video) GetPixel() uint8 { if vd.Playfield.priority { // priority 1 - if use, c := vd.Player0.pixel(); use { + if use, c := vd.Playfield.pixel(); use { return c } - if use, c := vd.Missile0.pixel(); use { + if use, c := vd.Ball.pixel(); use { return c } @@ -131,19 +129,18 @@ func (vd Video) GetPixel() uint8 { } // priority 3 - if use, c := vd.Playfield.pixel(); use { + if use, c := vd.Player0.pixel(); use { return c } - if use, c := vd.Ball.pixel(); use { + if use, c := vd.Missile0.pixel(); use { return c } - } else { // priority 1 - if use, c := vd.Playfield.pixel(); use { + if use, c := vd.Player0.pixel(); use { return c } - if use, c := vd.Ball.pixel(); use { + if use, c := vd.Missile0.pixel(); use { return c } @@ -156,10 +153,10 @@ func (vd Video) GetPixel() uint8 { } // priority 3 - if use, c := vd.Player0.pixel(); use { + if use, c := vd.Playfield.pixel(); use { return c } - if use, c := vd.Missile0.pixel(); use { + if use, c := vd.Ball.pixel(); use { return c } } @@ -168,6 +165,25 @@ func (vd Video) GetPixel() uint8 { return vd.Playfield.backgroundColor } +func createTriggerList(playerSize uint8) []int { + var triggerList []int + switch playerSize { + case 0x0, 0x05, 0x07: + // empty trigger list + case 0x01: + triggerList = []int{4} // 111100 + case 0x02: + triggerList = []int{8} // 110111 + case 0x03: + triggerList = []int{4, 8} // 111100, 110111 + case 0x04: + triggerList = []int{4} // 110111 + case 0x06: + triggerList = []int{8, 16} // 110111, 011100 + } + return triggerList +} + // ReadVideoMemory checks the TIA memory for changes to registers that are // interesting to the video sub-system. all changes happen immediately except // for those where a "schedule" function is called. @@ -175,10 +191,14 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool { switch register { case "NUSIZ0": vd.Missile0.size = (value & 0x30) >> 4 - // TODO: player width & trigger lists + vd.Player0.size = value & 0x07 + vd.Player0.triggerList = createTriggerList(vd.Player0.size) + vd.Missile0.triggerList = vd.Player0.triggerList case "NUSIZ1": vd.Missile1.size = (value & 0x30) >> 4 - // TODO: player width & trigger lists + vd.Player1.size = value & 0x07 + vd.Player1.triggerList = createTriggerList(vd.Player1.size) + vd.Missile1.triggerList = vd.Player1.triggerList case "COLUP0": vd.Player0.color = value & 0xfe vd.Missile0.color = value & 0xfe @@ -196,9 +216,9 @@ func (vd *Video) ReadVideoMemory(register string, value uint8) bool { vd.Playfield.scoremode = value&0x02 == 0x02 vd.Playfield.priority = value&0x04 == 0x04 case "REFP0": - vd.Player0.reflection = value&0x40 == 0x40 + vd.Player0.reflected = value&0x04 == 0x04 case "REFP1": - vd.Player1.reflection = value&0x40 == 0x40 + vd.Player1.reflected = value&0x04 == 0x04 case "PF0": vd.Playfield.scheduleWrite(0, value) case "PF1": diff --git a/hardware/vcs.go b/hardware/vcs.go index f9dfdc95..971398c9 100644 --- a/hardware/vcs.go +++ b/hardware/vcs.go @@ -92,28 +92,23 @@ func (vcs *VCS) Step(videoCycleCallback func(*cpu.InstructionResult) error) (int // TODO: not sure when in the video cycle sequence it should be run // TODO: is this something that can drift, thereby causing subtly different // results / graphical effects? is this what RSYNC is for? + vcs.RIOT.ReadRIOTMemory() vcs.RIOT.Step() // three color clocks per CPU cycle so we run video cycle three times vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle() - if vcs.MC.RdyFlg { - videoCycleCallback(r) - } + videoCycleCallback(r) vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle() - if vcs.MC.RdyFlg { - videoCycleCallback(r) - } + videoCycleCallback(r) // check for side effects from the CPU operation vcs.TIA.ReadTIAMemory() vcs.MC.RdyFlg = vcs.TIA.StepVideoCycle() - if vcs.MC.RdyFlg { - videoCycleCallback(r) - } + videoCycleCallback(r) } // TODO: full controller support -- this is emulating the rest state for the