diff --git a/src/emucore/tia/frame-manager/YStartDetector.cxx b/src/emucore/tia/frame-manager/YStartDetector.cxx new file mode 100644 index 000000000..01b68a0ec --- /dev/null +++ b/src/emucore/tia/frame-manager/YStartDetector.cxx @@ -0,0 +1,204 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "YStartDetector.hxx" +#include "TIAConstants.hxx" + +/** + * Misc. numeric constants used in the algorithm. + */ +enum Metrics: uInt32 { + frameLinesNTSC = 262, + frameLinesPAL = 312, + vblankNTSC = 37, + vblankPAL = 45, + waitForVsync = 32, + tvModeDetectionTolerance = 20, + maxUnderscan = 10, + maxVblankViolations = 2, + minStableVblankFrames = 1, + initialGarbageFrames = TIAConstants::initialGarbageFrames +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 YStartDetector::detectedYStart() const +{ + return myLastVblankLines; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void YStartDetector::onReset() +{ + myState = State::waitForVsyncStart; + myVblankMode = VblankMode::floating; + myLinesWaitingForVsyncToStart = 0; + myCurrentVblankLines = 0; + myLastVblankLines = 0; + myVblankViolations = 0; + myStableVblankFrames = 0; + myVblankViolated = false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void YStartDetector::onSetVsync() +{ + if (myVsync) + setState(State::waitForVsyncEnd); + else + setState(State::waitForFrameStart); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void YStartDetector::onNextLine() +{ + const uInt32 frameLines = layout() == FrameLayout::ntsc ? Metrics::frameLinesNTSC : Metrics::frameLinesPAL; + + switch (myState) { + case State::waitForVsyncStart: + // We start counting the number of "lines spent while waiting for vsync start" from + // the "ideal" frame size (corrected by the three scanlines spent in vsync). + if (myCurrentFrameTotalLines > frameLines - 3 || myTotalFrames == 0) + myLinesWaitingForVsyncToStart++; + + if (myLinesWaitingForVsyncToStart > Metrics::waitForVsync) setState(State::waitForVsyncEnd); + + break; + + case State::waitForVsyncEnd: + if (++myLinesWaitingForVsyncToStart > Metrics::waitForVsync) setState(State::waitForFrameStart); + + break; + + case State::waitForFrameStart: + if (shouldTransitionToFrame()) setState(State::waitForVsyncStart); + else myCurrentVblankLines++; + + break; + + default: + throw runtime_error("cannot happen"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool YStartDetector::shouldTransitionToFrame() +{ + uInt32 vblankLines = layout() == FrameLayout::pal ? Metrics::vblankPAL : Metrics::vblankNTSC; + + // Are we free to transition as per vblank cycle? + bool shouldTransition = myCurrentVblankLines + 1 >= (myVblank ? vblankLines : vblankLines - Metrics::maxUnderscan); + + // Do we **actually** transition? This depends on what mode we are in. + bool transition = false; + + switch (myVblankMode) { + // Floating mode: we still are looking for a stable frame start + case VblankMode::floating: + + // Are we free to transition? + if (shouldTransition) { + // Is this same scanline in which the transition ocurred last frame? + if (myTotalFrames > Metrics::initialGarbageFrames && myCurrentVblankLines == myLastVblankLines) + // Yes? -> Increase the number of stable frames + myStableVblankFrames++; + else + // No? -> Frame start shifted again, set the number of consecutive stable frames to zero + myStableVblankFrames = 0; + + // Save the transition point for checking on it next frame + myLastVblankLines = myCurrentVblankLines; + + // In floating mode, we transition whenever we can. + transition = true; + } + + // Transition to locked mode if we saw enough stable frames in a row. + if (myStableVblankFrames >= Metrics::minStableVblankFrames) { + myVblankMode = VblankMode::locked; + myVblankViolations = 0; + } + + break; + + // Locked mode: always transition at the same point, but check whether this is actually the + // detected transition point and revert state if applicable + case VblankMode::locked: + + // Have we reached the transition point? + if (myCurrentVblankLines == myLastVblankLines) { + + // Are we free to transition per the algorithm and didn't observe an violation before? + // (aka did the algorithm tell us to transition before reaching the actual line) + if (shouldTransition && !myVblankViolated) + // Reset the number of irregular frames (if any) + myVblankViolations = 0; + else { + // Record a violation if it wasn't recorded before + if (!myVblankViolated) myVblankViolations++; + myVblankViolated = true; + } + + // transition + transition = true; + // The algorithm tells us to transition although we haven't reached the trip line before + } else if (shouldTransition) { + // Record a violation if it wasn't recorded before + if (!myVblankViolated) myVblankViolations++; + myVblankViolated = true; + } + + // Revert to floating mode if there were too many irregular frames in a row + if (myVblankViolations > Metrics::maxVblankViolations) { + myVblankMode = VblankMode::floating; + myStableVblankFrames = 0; + } + + break; + + default: + throw runtime_error("cannot happen"); + } + + return transition; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void YStartDetector::setState(State state) +{ + if (state == myState) return; + + myState = state; + myLinesWaitingForVsyncToStart = 0; + + switch (state) { + case State::waitForVsyncEnd: + break; + + case State::waitForVsyncStart: + notifyFrameComplete(); + notifyFrameStart(); + break; + + case State::waitForFrameStart: + myVblankViolated = false; + myCurrentVblankLines = 0; + break; + + default: + throw new runtime_error("cannot happen"); + } +} diff --git a/src/emucore/tia/frame-manager/YStartDetector.hxx b/src/emucore/tia/frame-manager/YStartDetector.hxx new file mode 100644 index 000000000..121f9c39f --- /dev/null +++ b/src/emucore/tia/frame-manager/YStartDetector.hxx @@ -0,0 +1,85 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2017 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef TIA_YSTART_DETECTOR +#define TIA_YSTART_DETECTOR + +#include "AbstractFrameManager.hxx" + +class YStartDetector: public AbstractFrameManager { + + public: + + YStartDetector() = default; + + public: + + uInt32 detectedYStart() const; + + void setLayout(FrameLayout layout) override { this->layout(layout); } + + protected: + + void onSetVsync() override; + + void onReset() override; + + void onNextLine() override; + + private: + + enum State { + waitForVsyncStart, + waitForVsyncEnd, + waitForFrameStart + }; + + enum VblankMode { + locked, + floating + }; + + private: + + void setState(State state); + + bool shouldTransitionToFrame(); + + private: + + State myState; + + VblankMode myVblankMode; + + uInt32 myLinesWaitingForVsyncToStart; + + uInt32 myCurrentVblankLines; + uInt32 myLastVblankLines; + uInt32 myVblankViolations; + uInt32 myStableVblankFrames; + + bool myVblankViolated; + + private: + + YStartDetector(const YStartDetector&) = delete; + YStartDetector(YStartDetector&&) = delete; + YStartDetector& operator=(const YStartDetector&) = delete; + YStartDetector& operator=(YStartDetector&&) = delete; +}; + +#endif // TIA_YSTART_DETECTOR