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 | // #define TIA_FRAMEMANAGER_DEBUG_LOG |
19 | |
20 | #include <algorithm> |
21 | |
22 | #include "FrameManager.hxx" |
23 | |
24 | enum Metrics: uInt32 { |
25 | vblankNTSC = 37, |
26 | vblankPAL = 45, |
27 | kernelNTSC = 192, |
28 | kernelPAL = 228, |
29 | overscanNTSC = 30, |
30 | overscanPAL = 36, |
31 | vsync = 3, |
32 | maxLinesVsync = 50, |
33 | visibleOverscan = 20, |
34 | initialGarbageFrames = TIAConstants::initialGarbageFrames |
35 | }; |
36 | |
37 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
38 | FrameManager::FrameManager() |
39 | : myState(State::waitForVsyncStart), |
40 | myLineInState(0), |
41 | myVsyncLines(0), |
42 | myY(0), myLastY(0), |
43 | myVblankLines(0), |
44 | myKernelLines(0), |
45 | myOverscanLines(0), |
46 | myFrameLines(0), |
47 | myHeight(0), |
48 | myYStart(0), |
49 | myJitterEnabled(false) |
50 | { |
51 | reset(); |
52 | onLayoutChange(); |
53 | } |
54 | |
55 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
56 | void FrameManager::onReset() |
57 | { |
58 | myState = State::waitForVsyncStart; |
59 | myLineInState = 0; |
60 | myTotalFrames = 0; |
61 | myVsyncLines = 0; |
62 | myY = 0; |
63 | |
64 | myJitterEmulation.reset(); |
65 | } |
66 | |
67 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
68 | void FrameManager::onNextLine() |
69 | { |
70 | Int32 jitter; |
71 | |
72 | State previousState = myState; |
73 | ++myLineInState; |
74 | |
75 | switch (myState) |
76 | { |
77 | case State::waitForVsyncStart: |
78 | if ((myCurrentFrameTotalLines > myFrameLines - 3) || myTotalFrames == 0) |
79 | ++myVsyncLines; |
80 | |
81 | if (myVsyncLines > Metrics::maxLinesVsync) setState(State::waitForFrameStart); |
82 | |
83 | break; |
84 | |
85 | case State::waitForVsyncEnd: |
86 | if (++myVsyncLines > Metrics::maxLinesVsync) |
87 | setState(State::waitForFrameStart); |
88 | |
89 | break; |
90 | |
91 | case State::waitForFrameStart: |
92 | jitter = |
93 | (myJitterEnabled && myTotalFrames > Metrics::initialGarbageFrames) ? myJitterEmulation.jitter() : 0; |
94 | |
95 | if (myLineInState >= (myYStart + jitter)) setState(State::frame); |
96 | break; |
97 | |
98 | case State::frame: |
99 | if (myLineInState >= myHeight) |
100 | { |
101 | myLastY = ystart() + myY; // Last line drawn in this frame |
102 | setState(State::waitForVsyncStart); |
103 | } |
104 | break; |
105 | |
106 | default: |
107 | throw runtime_error("frame manager: invalid state" ); |
108 | } |
109 | |
110 | if (myState == State::frame && previousState == State::frame) ++myY; |
111 | } |
112 | |
113 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
114 | Int32 FrameManager::missingScanlines() const |
115 | { |
116 | if (myLastY == myYStart + myY) |
117 | return 0; |
118 | else { |
119 | return myHeight - myY; |
120 | } |
121 | } |
122 | |
123 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
124 | void FrameManager::setYstart(uInt32 ystart) |
125 | { |
126 | myYStart = ystart; |
127 | myJitterEmulation.setYStart(ystart); |
128 | } |
129 | |
130 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
131 | void FrameManager::onSetVsync() |
132 | { |
133 | if (myState == State::waitForVsyncEnd) setState(State::waitForFrameStart); |
134 | else setState(State::waitForVsyncEnd); |
135 | } |
136 | |
137 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
138 | void FrameManager::setState(FrameManager::State state) |
139 | { |
140 | if (myState == state) return; |
141 | |
142 | myState = state; |
143 | myLineInState = 0; |
144 | |
145 | switch (myState) { |
146 | case State::waitForFrameStart: |
147 | notifyFrameComplete(); |
148 | |
149 | if (myTotalFrames > Metrics::initialGarbageFrames) |
150 | myJitterEmulation.frameComplete(myCurrentFrameFinalLines); |
151 | |
152 | notifyFrameStart(); |
153 | |
154 | myVsyncLines = 0; |
155 | break; |
156 | |
157 | case State::frame: |
158 | myVsyncLines = 0; |
159 | myY = 0; |
160 | break; |
161 | |
162 | default: |
163 | break; |
164 | } |
165 | |
166 | updateIsRendering(); |
167 | } |
168 | |
169 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
170 | void FrameManager::onLayoutChange() |
171 | { |
172 | switch (layout()) |
173 | { |
174 | case FrameLayout::ntsc: |
175 | myVblankLines = Metrics::vblankNTSC; |
176 | myKernelLines = Metrics::kernelNTSC; |
177 | myOverscanLines = Metrics::overscanNTSC; |
178 | break; |
179 | |
180 | case FrameLayout::pal: |
181 | myVblankLines = Metrics::vblankPAL; |
182 | myKernelLines = Metrics::kernelPAL; |
183 | myOverscanLines = Metrics::overscanPAL; |
184 | break; |
185 | |
186 | default: |
187 | throw runtime_error("frame manager: invalid TV mode" ); |
188 | } |
189 | |
190 | myFrameLines = Metrics::vsync + myVblankLines + myKernelLines + myOverscanLines; |
191 | myHeight = myKernelLines + Metrics::visibleOverscan; |
192 | } |
193 | |
194 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
195 | void FrameManager::updateIsRendering() { |
196 | myIsRendering = myState == State::frame; |
197 | } |
198 | |
199 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
200 | bool FrameManager::onSave(Serializer& out) const |
201 | { |
202 | if (!myJitterEmulation.save(out)) return false; |
203 | |
204 | out.putInt(uInt32(myState)); |
205 | out.putInt(myLineInState); |
206 | out.putInt(myVsyncLines); |
207 | out.putInt(myY); |
208 | out.putInt(myLastY); |
209 | |
210 | out.putInt(myVblankLines); |
211 | out.putInt(myKernelLines); |
212 | out.putInt(myOverscanLines); |
213 | out.putInt(myFrameLines); |
214 | out.putInt(myHeight); |
215 | out.putInt(myYStart); |
216 | |
217 | out.putBool(myJitterEnabled); |
218 | |
219 | return true; |
220 | } |
221 | |
222 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
223 | bool FrameManager::onLoad(Serializer& in) |
224 | { |
225 | if (!myJitterEmulation.load(in)) return false; |
226 | |
227 | myState = State(in.getInt()); |
228 | myLineInState = in.getInt(); |
229 | myVsyncLines = in.getInt(); |
230 | myY = in.getInt(); |
231 | myLastY = in.getInt(); |
232 | |
233 | myVblankLines = in.getInt(); |
234 | myKernelLines = in.getInt(); |
235 | myOverscanLines = in.getInt(); |
236 | myFrameLines = in.getInt(); |
237 | myHeight = in.getInt(); |
238 | myYStart = in.getInt(); |
239 | |
240 | myJitterEnabled = in.getBool(); |
241 | |
242 | return true; |
243 | } |
244 | |