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 */
24enum 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
50YStartDetector::YStartDetector()
51 : myState(State::waitForVsyncStart),
52 myVblankMode(VblankMode::floating),
53 myVblankViolated(false)
54{
55 reset();
56}
57
58// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
59uInt32 YStartDetector::detectedYStart() const
60{
61 return myLastVblankLines;
62}
63
64// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
65void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
78void YStartDetector::onSetVsync()
79{
80 if (myVsync)
81 setState(State::waitForVsyncEnd);
82 else
83 setState(State::waitForFrameStart);
84}
85
86// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
87void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
119bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
201void 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