1 | //============================================================================ |
2 | // |
3 | // SSSS tt lll lll |
4 | // SS SS tt ll ll |
5 | // SS tttttt eeee ll ll aaaa |
6 | // SSSS tt ee ee ll ll aa |
7 | // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" |
8 | // SS SS tt ee ll ll aa aa |
9 | // SSSS ttt eeeee llll llll aaaaa |
10 | // |
11 | // Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony |
12 | // and the Stella Team |
13 | // |
14 | // See the file "License.txt" for information on usage and redistribution of |
15 | // this file, and for a DISCLAIMER OF ALL WARRANTIES. |
16 | //============================================================================ |
17 | |
18 | #include "YStartDetector.hxx" |
19 | #include "TIAConstants.hxx" |
20 | |
21 | /** |
22 | * Misc. numeric constants used in the algorithm. |
23 | */ |
24 | enum Metrics: uInt32 { |
25 | // ideal world frame sizes |
26 | frameLinesNTSC = 262, |
27 | frameLinesPAL = 312, |
28 | |
29 | // the ideal vblank zone |
30 | vblankNTSC = 37, |
31 | vblankPAL = 45, |
32 | |
33 | // number of scanlines to wait for vsync to start (exceeding after the ideal frame size) and stop |
34 | waitForVsync = 50, |
35 | |
36 | // max lines underscan |
37 | maxUnderscan = 10, |
38 | |
39 | // max lines deviations from detected ystart before we switch back to floating |
40 | maxVblankViolations = 2, |
41 | |
42 | // switch to fixed mode after this number of stable frames (+1) |
43 | minStableVblankFrames = 1, |
44 | |
45 | // no transitions to fixed mode will happend during those |
46 | initialGarbageFrames = TIAConstants::initialGarbageFrames |
47 | }; |
48 | |
49 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
50 | YStartDetector::YStartDetector() |
51 | : myState(State::waitForVsyncStart), |
52 | myVblankMode(VblankMode::floating), |
53 | myVblankViolated(false) |
54 | { |
55 | reset(); |
56 | } |
57 | |
58 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
59 | uInt32 YStartDetector::detectedYStart() const |
60 | { |
61 | return myLastVblankLines; |
62 | } |
63 | |
64 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
65 | void YStartDetector::onReset() |
66 | { |
67 | myState = State::waitForVsyncStart; |
68 | myVblankMode = VblankMode::floating; |
69 | myLinesWaitingForVsyncToStart = 0; |
70 | myCurrentVblankLines = 0; |
71 | myLastVblankLines = 0; |
72 | myVblankViolations = 0; |
73 | myStableVblankFrames = 0; |
74 | myVblankViolated = false; |
75 | } |
76 | |
77 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
78 | void YStartDetector::onSetVsync() |
79 | { |
80 | if (myVsync) |
81 | setState(State::waitForVsyncEnd); |
82 | else |
83 | setState(State::waitForFrameStart); |
84 | } |
85 | |
86 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
87 | void YStartDetector::onNextLine() |
88 | { |
89 | const uInt32 frameLines = layout() == FrameLayout::ntsc ? Metrics::frameLinesNTSC : Metrics::frameLinesPAL; |
90 | |
91 | switch (myState) { |
92 | case State::waitForVsyncStart: |
93 | // We start counting the number of "lines spent while waiting for vsync start" from |
94 | // the "ideal" frame size (corrected by the three scanlines spent in vsync). |
95 | if (myCurrentFrameTotalLines > frameLines - 3 || myTotalFrames == 0) |
96 | ++myLinesWaitingForVsyncToStart; |
97 | |
98 | if (myLinesWaitingForVsyncToStart > Metrics::waitForVsync) setState(State::waitForVsyncEnd); |
99 | |
100 | break; |
101 | |
102 | case State::waitForVsyncEnd: |
103 | if (++myLinesWaitingForVsyncToStart > Metrics::waitForVsync) setState(State::waitForFrameStart); |
104 | |
105 | break; |
106 | |
107 | case State::waitForFrameStart: |
108 | if (shouldTransitionToFrame()) setState(State::waitForVsyncStart); |
109 | else ++myCurrentVblankLines; |
110 | |
111 | break; |
112 | |
113 | default: |
114 | throw runtime_error("cannot happen" ); |
115 | } |
116 | } |
117 | |
118 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
119 | bool YStartDetector::shouldTransitionToFrame() |
120 | { |
121 | uInt32 vblankLines = layout() == FrameLayout::pal ? Metrics::vblankPAL : Metrics::vblankNTSC; |
122 | |
123 | // Are we free to transition as per vblank cycle? |
124 | bool shouldTransition = myCurrentVblankLines + 1 >= (myVblank ? vblankLines : vblankLines - Metrics::maxUnderscan); |
125 | |
126 | // Do we **actually** transition? This depends on what mode we are in. |
127 | bool transition = false; |
128 | |
129 | switch (myVblankMode) { |
130 | // Floating mode: we still are looking for a stable frame start |
131 | case VblankMode::floating: |
132 | |
133 | // Are we free to transition? |
134 | if (shouldTransition) { |
135 | // Is this same scanline in which the transition ocurred last frame? |
136 | if (myTotalFrames > Metrics::initialGarbageFrames && myCurrentVblankLines == myLastVblankLines) |
137 | // Yes? -> Increase the number of stable frames |
138 | ++myStableVblankFrames; |
139 | else |
140 | // No? -> Frame start shifted again, set the number of consecutive stable frames to zero |
141 | myStableVblankFrames = 0; |
142 | |
143 | // Save the transition point for checking on it next frame |
144 | myLastVblankLines = myCurrentVblankLines; |
145 | |
146 | // In floating mode, we transition whenever we can. |
147 | transition = true; |
148 | } |
149 | |
150 | // Transition to locked mode if we saw enough stable frames in a row. |
151 | if (myStableVblankFrames >= Metrics::minStableVblankFrames) { |
152 | myVblankMode = VblankMode::locked; |
153 | myVblankViolations = 0; |
154 | } |
155 | |
156 | break; |
157 | |
158 | // Locked mode: always transition at the same point, but check whether this is actually the |
159 | // detected transition point and revert state if applicable |
160 | case VblankMode::locked: |
161 | |
162 | // Have we reached the transition point? |
163 | if (myCurrentVblankLines == myLastVblankLines) { |
164 | |
165 | // Are we free to transition per the algorithm and didn't observe an violation before? |
166 | // (aka did the algorithm tell us to transition before reaching the actual line) |
167 | if (shouldTransition && !myVblankViolated) |
168 | // Reset the number of irregular frames (if any) |
169 | myVblankViolations = 0; |
170 | else { |
171 | // Record a violation if it wasn't recorded before |
172 | if (!myVblankViolated) ++myVblankViolations; |
173 | myVblankViolated = true; |
174 | } |
175 | |
176 | // transition |
177 | transition = true; |
178 | // The algorithm tells us to transition although we haven't reached the trip line before |
179 | } else if (shouldTransition) { |
180 | // Record a violation if it wasn't recorded before |
181 | if (!myVblankViolated) ++myVblankViolations; |
182 | myVblankViolated = true; |
183 | } |
184 | |
185 | // Revert to floating mode if there were too many irregular frames in a row |
186 | if (myVblankViolations > Metrics::maxVblankViolations) { |
187 | myVblankMode = VblankMode::floating; |
188 | myStableVblankFrames = 0; |
189 | } |
190 | |
191 | break; |
192 | |
193 | default: |
194 | throw runtime_error("cannot happen" ); |
195 | } |
196 | |
197 | return transition; |
198 | } |
199 | |
200 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
201 | void YStartDetector::setState(State state) |
202 | { |
203 | if (state == myState) return; |
204 | |
205 | myState = state; |
206 | myLinesWaitingForVsyncToStart = 0; |
207 | |
208 | switch (state) { |
209 | case State::waitForVsyncEnd: |
210 | break; |
211 | |
212 | case State::waitForVsyncStart: |
213 | notifyFrameComplete(); |
214 | notifyFrameStart(); |
215 | break; |
216 | |
217 | case State::waitForFrameStart: |
218 | myVblankViolated = false; |
219 | myCurrentVblankLines = 0; |
220 | break; |
221 | |
222 | default: |
223 | throw new runtime_error("cannot happen" ); |
224 | } |
225 | } |
226 | |