| 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 |  | 
|---|