-
Notifications
You must be signed in to change notification settings - Fork 20
/
scancounter.go
157 lines (140 loc) · 4.76 KB
/
scancounter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package video
import (
"github.com/jetsetilly/gopher2600/hardware/tia/phaseclock"
)
// scancounter is the mechanism for outputting player sprite pixels. it is the
// equivalent of the enclockifier type used by the ball and missile sprite.
// scancounter is used only by the player sprite
//
// once a player sprite has reached a START signal during its polycounter
// cycle, the scanCounter is started and is ticked forward every cycle (subject
// to MOTCK, HMOVE and NUSIZ rules).
type scanCounter struct {
sizeAndCopies *uint8
pclk *phaseclock.PhaseClock
// LatchesSizeAndCopies is used to decide how often to tick the scan
// counter onto the next pixel (after the additional latching, see below).
// in most situations we could use the live nusiz value (see above) but
// resetting the player sprite while the scan counter is active requires
// some special handling
LatchedSizeAndCopies uint8
// number of additional ticks required before drawing begins
latch int
// Pixel counts from 7 to -1 for a total of 8 active pixels. we're counting
// backwards because it is more convenient for the Pixel() function
Pixel int
// for the wider player sizes, real ticks are only made every two or every
// four clocks. 'count' records how many ticks the scanCounter has been on
// the current pixel value
count int
// which copy of the sprite is being drawn. value of zero means the primary
// copy is being drawn (if enable is true)
Cpy int
}
func (sc *scanCounter) start() {
if sc.LatchedSizeAndCopies == 0x05 || sc.LatchedSizeAndCopies == 0x07 {
sc.latch = 2
} else {
sc.latch = 1
}
}
// IsActive returns true is scan counter is currently outputting pixels.
func (sc *scanCounter) IsActive() bool {
return sc.Pixel != -1
}
// IsLatching returns true if scan counter is about to commence outputting
// pixels.
func (sc *scanCounter) IsLatching() bool {
return sc.latch > 0
}
func (sc *scanCounter) tick() {
// handle the additional latching
if sc.latch > 0 {
sc.latch--
if sc.latch == 0 {
sc.count = 0
sc.Pixel = 7
sc.LatchedSizeAndCopies = *sc.sizeAndCopies
}
return
}
// tick pixels differently depending on whether this is the primary copy or
// a secondary copy. this is all a bit magical for my liking but it
// works and there's some sense to it at least.
//
// for the primary copy, we delay the use of the live nusiz value until the
// correct clock phase. once the nusiz value has been latched then we tick
// according to how long the scancounter has been on the current pixel
//
// for the secondary copies we always use the live nusiz value and a skewed
// phase-clock. not sure why the skewed clock is required but the effects
// can clearly be seen with test/test_roms/testSize2Copies_B.bin
if sc.Cpy == 0 {
// latch the nusiz value depending on the phase of the player clock
switch *sc.sizeAndCopies {
case 0x05:
if *sc.pclk == phaseclock.RisingPhi1 || *sc.pclk == phaseclock.RisingPhi2 {
sc.LatchedSizeAndCopies = *sc.sizeAndCopies
}
case 0x07:
if *sc.pclk == phaseclock.RisingPhi1 {
sc.LatchedSizeAndCopies = *sc.sizeAndCopies
}
default:
sc.LatchedSizeAndCopies = *sc.sizeAndCopies
}
switch sc.LatchedSizeAndCopies {
case 0x05:
if sc.count < 1 {
sc.count++
return
}
case 0x07:
if sc.count < 3 {
sc.count++
return
}
}
} else {
// timing of ticks for non-primary copies is skewed
switch *sc.sizeAndCopies {
case 0x05:
if !(*sc.pclk == phaseclock.FallingPhi1 || *sc.pclk == phaseclock.FallingPhi2) {
sc.count++
return
}
case 0x07:
if *sc.pclk != phaseclock.FallingPhi2 {
sc.count++
return
}
}
}
if sc.Pixel >= 0 {
sc.count = 0
sc.Pixel--
// default to primary copy whenever we finish drawing. we need this
// otherwise the above branch, sc.cpy == 0, will not trigger
// correctly in all instances - if we look at the Player.tick()
// function we can see why. scanCounter.cpy is set only when the
// startDrawingEvent has concluded but we need to update the
// latchedSizeAndCopies value for the primary copy before then.
if sc.Pixel < 0 {
sc.Cpy = 0
}
}
}