diff --git a/.gitignore b/.gitignore index e78ff888a..047f4b523 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ project.xcworkspace/ xcuserdata/ build/ src/macosx/M6502.ins +*.dSYM diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 1a1d89422..b215087f1 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -14,6 +14,7 @@ "${workspaceRoot}/src/gui", "${workspaceRoot}/src/emucore", "${workspaceRoot}/src/emucore/tia", + "${workspaceRoot}/src/emucore/tia/frame-manager", "${workspaceRoot}/src/unix", "${workspaceRoot}/src/debugger", "${workspaceRoot}/src/debugger/gui", diff --git a/.vscode/settings.json b/.vscode/settings.json index 857d47127..3ae42370f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,9 @@ "editor.trimAutoWhitespace": true, "editor.useTabStops": false, "C_Cpp.intelliSenseEngine": "Default", - "files.insertFinalNewline": true + "files.insertFinalNewline": true, + "files.associations": { + "locale": "cpp", + "string": "cpp" + } } diff --git a/Makefile b/Makefile index 0754b5dee..df22b102c 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,7 @@ MODULES := $(MODULES) MODULES += \ src/emucore \ src/emucore/tia \ + src/emucore/tia/frame-manager \ src/gui \ src/common \ src/common/tv_filters diff --git a/configure b/configure index 2c3fe051e..648452860 100755 --- a/configure +++ b/configure @@ -691,6 +691,7 @@ SRC="src" CORE="$SRC/emucore" COMMON="$SRC/common" TIA="$SRC/emucore/tia" +TIA_FRAME_MANAGER="$SRC/emucore/tia/frame-manager" TV="$SRC/common/tv_filters" GUI="$SRC/gui" DBG="$SRC/debugger" @@ -700,7 +701,7 @@ CHEAT="$SRC/cheat" LIBPNG="$SRC/libpng" ZLIB="$SRC/zlib" -INCLUDES="-I$CORE -I$COMMON -I$TV -I$GUI -I$TIA" +INCLUDES="-I$CORE -I$COMMON -I$TV -I$GUI -I$TIA -I$TIA_FRAME_MANAGER" INCLUDES="$INCLUDES `$_sdlconfig --cflags`" if test "$_build_static" = yes ; then diff --git a/src/debugger/TIADebug.cxx b/src/debugger/TIADebug.cxx index cefffdcb6..69611e7a8 100644 --- a/src/debugger/TIADebug.cxx +++ b/src/debugger/TIADebug.cxx @@ -48,7 +48,7 @@ const DebuggerState& TIADebug::getState() myState.coluRegs.push_back(coluBK()); // Debug Colors - int mode = myTIA.myFrameManager.layout() == FrameLayout::ntsc ? 0 : 1; + int mode = myTIA.frameLayout() == FrameLayout::ntsc ? 0 : 1; myState.fixedCols.clear(); myState.fixedCols.push_back(myTIA.myFixedColorPalette[mode][TIA::P0]); myState.fixedCols.push_back(myTIA.myFixedColorPalette[mode][TIA::P1]); @@ -721,7 +721,7 @@ int TIADebug::scanlines() const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int TIADebug::scanlinesLastFrame() const { - return myTIA.myFrameManager.scanlinesLastFrame(); + return myTIA.scanlinesLastFrame(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -807,7 +807,7 @@ string TIADebug::debugColors() const { ostringstream buf; - int mode = myTIA.myFrameManager.layout() == FrameLayout::ntsc ? 0 : 1; + int mode = myTIA.frameLayout() == FrameLayout::ntsc ? 0 : 1; buf << " Red " << colorSwatch(myTIA.myFixedColorPalette[mode][TIA::P0]) << " Player 0\n" << " Orange " << colorSwatch(myTIA.myFixedColorPalette[mode][TIA::M0]) diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index 9d8d48b71..c62666827 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -55,8 +55,11 @@ #include "CommandMenu.hxx" #include "Serializable.hxx" #include "Version.hxx" -#include "FrameManager.hxx" +#include "TIAConstants.hxx" #include "FrameLayout.hxx" +#include "frame-manager/FrameManager.hxx" +#include "frame-manager/FrameLayoutDetector.hxx" +#include "frame-manager/YStartDetector.hxx" #ifdef DEBUGGER_SUPPORT #include "Debugger.hxx" @@ -77,7 +80,8 @@ Console::Console(OSystem& osystem, unique_ptr& cart, myCart(std::move(cart)), myDisplayFormat(""), // Unknown TV format @ start myFramerate(0.0), // Unknown framerate @ start - myCurrentFormat(0), // Unknown format @ start + myCurrentFormat(0), // Unknown format @ start, + myAutodetectedYstart(0), myUserPaletteDefined(false), myConsoleTiming(ConsoleTiming::ntsc) { @@ -88,8 +92,11 @@ Console::Console(OSystem& osystem, unique_ptr& cart, my6502 = make_unique(myOSystem.settings()); myRiot = make_unique(*this, myOSystem.settings()); myTIA = make_unique(*this, myOSystem.sound(), myOSystem.settings()); + myFrameManager = make_unique(); mySwitches = make_unique(myEvent, myProperties); + myTIA->setFrameManager(myFrameManager.get()); + // Construct the system and components mySystem = make_unique(osystem, *my6502, *myRiot, *myTIA, *myCart); @@ -114,36 +121,19 @@ Console::Console(OSystem& osystem, unique_ptr& cart, if(myDisplayFormat == "AUTO" || myOSystem.settings().getBool("rominfo")) { - // Run the TIA, looking for PAL scanline patterns - // We turn off the SuperCharger progress bars, otherwise the SC BIOS - // will take over 250 frames! - // The 'fastscbios' option must be changed before the system is reset - bool fastscbios = myOSystem.settings().getBool("fastscbios"); - myOSystem.settings().setValue("fastscbios", true); + autodetectFrameLayout(); - uInt8 initialGarbageFrames = FrameManager::initialGarbageFrames(); - uInt8 linesPAL = 0; - uInt8 linesNTSC = 0; - - mySystem->reset(true); // autodetect in reset enabled - myTIA->autodetectLayout(true); - for(int i = 0; i < 60; ++i) { - if (i > initialGarbageFrames) - myTIA->frameLayout() == FrameLayout::pal ? linesPAL++ : linesNTSC++; - - myTIA->update(); - } - - myDisplayFormat = linesPAL > linesNTSC ? "PAL" : "NTSC"; if(myProperties.get(Display_Format) == "AUTO") { autodetected = "*"; myCurrentFormat = 0; } - - // Don't forget to reset the SC progress bars again - myOSystem.settings().setValue("fastscbios", fastscbios); } + + if (atoi(myProperties.get(Display_YStart).c_str()) == 0) { + autodetectYStart(); + } + myConsoleInfo.DisplayFormat = myDisplayFormat + autodetected; // Set up the correct properties used when toggling format @@ -218,6 +208,54 @@ Console::~Console() myRightControl->close(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Console::autodetectFrameLayout() +{ + // Run the TIA, looking for PAL scanline patterns + // We turn off the SuperCharger progress bars, otherwise the SC BIOS + // will take over 250 frames! + // The 'fastscbios' option must be changed before the system is reset + bool fastscbios = myOSystem.settings().getBool("fastscbios"); + myOSystem.settings().setValue("fastscbios", true); + + FrameLayoutDetector frameLayoutDetector; + myTIA->setFrameManager(&frameLayoutDetector); + mySystem->reset(true); + + for(int i = 0; i < 60; ++i) myTIA->update(); + + myTIA->setFrameManager(myFrameManager.get()); + + myDisplayFormat = frameLayoutDetector.detectedLayout() == FrameLayout::pal ? "PAL" : "NTSC"; + + // Don't forget to reset the SC progress bars again + myOSystem.settings().setValue("fastscbios", fastscbios); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Console::autodetectYStart() +{ + // We turn off the SuperCharger progress bars, otherwise the SC BIOS + // will take over 250 frames! + // The 'fastscbios' option must be changed before the system is reset + bool fastscbios = myOSystem.settings().getBool("fastscbios"); + myOSystem.settings().setValue("fastscbios", true); + + YStartDetector ystartDetector; + ystartDetector.setLayout(myDisplayFormat == "PAL" ? FrameLayout::pal : FrameLayout::ntsc); + myTIA->setFrameManager(&ystartDetector); + mySystem->reset(); + + for (int i = 0; i < 80; i++) myTIA->update(); + + myTIA->setFrameManager(myFrameManager.get()); + + myAutodetectedYstart = ystartDetector.detectedYStart(); + + // Don't forget to reset the SC progress bars again + myOSystem.settings().setValue("fastscbios", fastscbios); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool Console::save(Serializer& out) const { @@ -570,7 +608,7 @@ void Console::changeYStart(int direction) if(direction == +1) // increase YStart { - if(ystart >= FrameManager::maxYStart) + if(ystart >= TIAConstants::maxYStart) { myOSystem.frameBuffer().showMessage("YStart at maximum"); return; @@ -579,11 +617,17 @@ void Console::changeYStart(int direction) } else if(direction == -1) // decrease YStart { - if(ystart == FrameManager::minYStart-1) + if (ystart == TIAConstants::minYStart && myAutodetectedYstart == 0) { + myOSystem.frameBuffer().showMessage("Autodetected YStart not available"); + return; + } + + if(ystart == TIAConstants::minYStart-1 && myAutodetectedYstart > 0) { myOSystem.frameBuffer().showMessage("YStart at minimum"); return; } + ystart--; } else @@ -591,16 +635,16 @@ void Console::changeYStart(int direction) ostringstream val; val << ystart; - if(ystart == FrameManager::minYStart-1) + if(ystart == TIAConstants::minYStart-1) myOSystem.frameBuffer().showMessage("YStart autodetected"); else { - if(myTIA->ystartIsAuto(ystart)) + if(myAutodetectedYstart > 0 && myAutodetectedYstart == ystart) { // We've reached the auto-detect value, so reset myOSystem.frameBuffer().showMessage("YStart " + val.str() + " (Auto)"); val.str(""); - val << FrameManager::minYStart-1; + val << TIAConstants::minYStart-1; } else myOSystem.frameBuffer().showMessage("YStart " + val.str()); @@ -620,7 +664,7 @@ void Console::changeHeight(int direction) if(direction == +1) // increase Height { height++; - if(height > FrameManager::maxViewableHeight || height > dheight) + if(height > TIAConstants::maxViewableHeight || height > dheight) { myOSystem.frameBuffer().showMessage("Height at maximum"); return; @@ -629,7 +673,7 @@ void Console::changeHeight(int direction) else if(direction == -1) // decrease Height { height--; - if(height < FrameManager::minViewableHeight) height = 0; + if(height < TIAConstants::minViewableHeight) height = 0; } else return; @@ -649,12 +693,10 @@ void Console::setTIAProperties() { uInt32 ystart = atoi(myProperties.get(Display_YStart).c_str()); if(ystart != 0) - ystart = BSPF::clamp(ystart, FrameManager::minYStart, FrameManager::maxYStart); + ystart = BSPF::clamp(ystart, TIAConstants::minYStart, TIAConstants::maxYStart); uInt32 height = atoi(myProperties.get(Display_Height).c_str()); if(height != 0) - height = BSPF::clamp(height, FrameManager::minViewableHeight, FrameManager::maxViewableHeight); - - myTIA->autodetectLayout(false); + height = BSPF::clamp(height, TIAConstants::minViewableHeight, TIAConstants::maxViewableHeight); if(myDisplayFormat == "NTSC" || myDisplayFormat == "PAL60" || myDisplayFormat == "SECAM60") @@ -676,7 +718,7 @@ void Console::setTIAProperties() myTIA->setLayout(FrameLayout::pal); } - myTIA->setYStart(ystart); + myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart); myTIA->setHeight(height); } diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx index 8f4e9c758..baf71ce35 100644 --- a/src/emucore/Console.hxx +++ b/src/emucore/Console.hxx @@ -35,6 +35,7 @@ class Debugger; #include "FrameBuffer.hxx" #include "Serializable.hxx" #include "NTSCFilter.hxx" +#include "frame-manager/AbstractFrameManager.hxx" /** Contains detailed info about a console. @@ -304,6 +305,16 @@ class Console : public Serializable void toggleJitter() const; private: + /** + * Dry-run the emulation and detect the frame layout (PAL / NTSC). + */ + void autodetectFrameLayout(); + + /** + * Dryrun the emulation and detect ystart (the first visible scanline). + */ + void autodetectYStart(); + /** Sets various properties of the TIA (YStart, Height, etc) based on the current display format. @@ -366,6 +377,9 @@ class Console : public Serializable // Pointer to the TIA object unique_ptr myTIA; + // The frame manager instance that is used during emulation. + unique_ptr myFrameManager; + // Pointer to the Cartridge (the debugger needs it) unique_ptr myCart; @@ -387,6 +401,9 @@ class Console : public Serializable // Display format currently in use uInt32 myCurrentFormat; + // Autodetected ystart. + uInt32 myAutodetectedYstart; + // Indicates whether an external palette was found and // successfully loaded bool myUserPaletteDefined; diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index d13642012..11a8318b6 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -33,7 +33,7 @@ namespace GUI { #include "Variant.hxx" #include "FBSurface.hxx" #include "TIASurface.hxx" -#include "FrameManager.hxx" +#include "TIAConstants.hxx" #include "bspf.hxx" // Return values for initialization of framebuffer window @@ -132,7 +132,7 @@ class FrameBuffer { public: enum { - kTIAMinW = 320u, kTIAMinH = FrameManager::minViewableHeight, + kTIAMinW = 320u, kTIAMinH = TIAConstants::minViewableHeight, kFBMinW = 640u, kFBMinH = 480u }; diff --git a/src/emucore/TIASurface.hxx b/src/emucore/TIASurface.hxx index 5bdc9c747..6cee9eac2 100644 --- a/src/emucore/TIASurface.hxx +++ b/src/emucore/TIASurface.hxx @@ -31,6 +31,7 @@ class VideoMode; #include "Rect.hxx" #include "NTSCFilter.hxx" #include "bspf.hxx" +#include "TIAConstants.hxx" /** This class is basically a wrapper around all things related to rendering @@ -173,9 +174,9 @@ class TIASurface }; Filter myFilter; - enum TIAConstants { + enum { kTIAW = 160, - kTIAH = FrameManager::frameBufferHeight, + kTIAH = TIAConstants::frameBufferHeight, kScanH = kTIAH*2 }; diff --git a/src/emucore/tia/FrameManager.cxx b/src/emucore/tia/FrameManager.cxx deleted file mode 100644 index 91312a6a0..000000000 --- a/src/emucore/tia/FrameManager.cxx +++ /dev/null @@ -1,488 +0,0 @@ -//============================================================================ -// -// 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. -//============================================================================ - -// #define TIA_FRAMEMANAGER_DEBUG_LOG - -#include - -#include "FrameManager.hxx" - -enum Metrics: uInt32 { - vblankNTSC = 37, - vblankPAL = 45, - kernelNTSC = 192, - kernelPAL = 228, - overscanNTSC = 30, - overscanPAL = 36, - vsync = 3, - maxLinesVsync = 32, - maxLinesVsyncDuringAutodetect = 100, - visibleOverscan = 20, - tvModeDetectionTolerance = 20, - initialGarbageFrames = 10, - framesForModeConfirmation = 5, - minStableFrames = 10, - maxStabilizationFrames = 20, - minDeltaForJitter = 3, - framesForStableHeight = 2 -}; - -static constexpr uInt32 - frameLinesNTSC = Metrics::vsync + Metrics::vblankNTSC + Metrics::kernelNTSC + Metrics::overscanNTSC, - frameLinesPAL = Metrics::vsync + Metrics::vblankPAL + Metrics::kernelPAL + Metrics::overscanPAL; - -inline static uInt32 vsyncLimit(bool autodetect) { - return autodetect ? maxLinesVsyncDuringAutodetect : maxLinesVsync; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 FrameManager::initialGarbageFrames() -{ - return Metrics::initialGarbageFrames; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -FrameManager::FrameManager() - : myLayout(FrameLayout::pal), - myAutodetectLayout(true), - myHeight(0), - myFixedHeight(0), - myJitterEnabled(false) -{ - updateLayout(FrameLayout::ntsc); - reset(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::setHandlers( - FrameManager::callback frameStartCallback, - FrameManager::callback frameCompleteCallback, - FrameManager::callback renderingStartCallback -) -{ - myOnFrameStart = frameStartCallback; - myOnFrameComplete = frameCompleteCallback; - myOnRenderingStart = renderingStartCallback; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::reset() -{ - myVblankManager.reset(); - - myState = State::waitForVsyncStart; - myCurrentFrameTotalLines = myCurrentFrameFinalLines = 0; - myFrameRate = 60.0; - myLineInState = 0; - myVsync = false; - myTotalFrames = 0; - myFramesInMode = 0; - myModeConfirmed = false; - myVsyncLines = 0; - myY = 0; - myFramePending = false; - myStabilizationFrames = 0; - myStableFrames = 0; - myHasStabilized = false; - - myStableFrameLines = -1; - myStableFrameHeightCountdown = 0; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::nextLine() -{ - State previousState = myState; - - myCurrentFrameTotalLines++; - myLineInState++; - - switch (myState) - { - case State::waitForVsyncStart: - if ((myCurrentFrameTotalLines > myFrameLines - 3) || myTotalFrames == 0) - myVsyncLines++; - - if (myVsyncLines > vsyncLimit(myAutodetectLayout)) setState(State::waitForFrameStart); - - break; - - case State::waitForVsyncEnd: - if (++myVsyncLines > vsyncLimit(myAutodetectLayout)) - setState(State::waitForFrameStart); - - break; - - case State::waitForFrameStart: - if (myVblankManager.nextLine(myTotalFrames <= Metrics::initialGarbageFrames)) - setState(State::frame); - break; - - case State::frame: - if (myLineInState >= myHeight) - { - myLastY = ystart() + myY; // Last line drawn in this frame - setState(State::waitForVsyncStart); - } - break; - -// default: -// throw runtime_error("frame manager: invalid state"); - } - - if (myState == State::frame && previousState == State::frame) myY++; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 FrameManager::missingScanlines() const -{ - if (myLastY == ystart() + myY) - return 0; - else - return myHeight - myY; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::setVsync(bool vsync) -{ - if (vsync == myVsync) return; - -#ifdef TIA_FRAMEMANAGER_DEBUG_LOG - (cout << "vsync " << myVsync << " -> " << vsync << ": state " << int(myState) << " @ " << myLineInState << "\n").flush(); -#endif - - myVsync = vsync; - - switch (myState) - { - case State::waitForVsyncStart: - case State::waitForFrameStart: - if (myVsync) setState(State::waitForVsyncEnd); - break; - - case State::waitForVsyncEnd: - if (!myVsync) setState(State::waitForFrameStart); - break; - - case State::frame: - if (myVsync) setState(State::waitForVsyncEnd); - break; - -// default: -// throw runtime_error("frame manager: invalid state"); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::setState(FrameManager::State state) -{ - if (myState == state) return; - -#ifdef TIA_FRAMEMANAGER_DEBUG_LOG - (cout << "state change " << myState << " -> " << state << " @ " << myLineInState << "\n").flush(); -#endif // TIA_FRAMEMANAGER_DEBUG_LOG - - myState = state; - myLineInState = 0; - - switch (myState) { - case State::waitForFrameStart: - if (!myHasStabilized) { - myHasStabilized = - myStableFrames >= Metrics::minStableFrames || - myStabilizationFrames >= Metrics::maxStabilizationFrames; - - myStabilizationFrames++; - - if (myVblankManager.isStable()) - myStableFrames++; - else - myStableFrames = 0; - } - - if (myFramePending) finalizeFrame(); - if (myOnFrameStart) myOnFrameStart(); - - myVblankManager.start(); - myFramePending = true; - - myVsyncLines = 0; - break; - - case State::frame: - if (myOnRenderingStart) myOnRenderingStart(); - myVsyncLines = 0; - myY = 0; - break; - - default: - break; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::finalizeFrame() -{ - if (myCurrentFrameTotalLines != uInt32(myStableFrameLines)) { - if (myCurrentFrameTotalLines == myCurrentFrameFinalLines) { - - if (++myStableFrameHeightCountdown >= Metrics::framesForStableHeight) { - if (myStableFrameLines >= 0) { - handleJitter(myCurrentFrameTotalLines - myStableFrameLines); - } - - myStableFrameLines = myCurrentFrameTotalLines; - } - - } - else myStableFrameHeightCountdown = 0; - } - - myPreviousFrameFinalLines = myCurrentFrameFinalLines; - myCurrentFrameFinalLines = myCurrentFrameTotalLines; - myCurrentFrameTotalLines = 0; - myTotalFrames++; - - if (myOnFrameComplete) myOnFrameComplete(); - -#ifdef TIA_FRAMEMANAGER_DEBUG_LOG - (cout << "frame complete @ " << myLineInState << " (" << myCurrentFrameFinalLines << " total)" << "\n").flush(); -#endif // TIA_FRAMEMANAGER_DEBUG_LOG - - if (myAutodetectLayout) updateAutodetectedLayout(); - - myFrameRate = (myLayout == FrameLayout::pal ? 15600.0 : 15720.0) / - myCurrentFrameFinalLines; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::handleJitter(Int32 scanlineDifference) -{ - if ( - uInt32(abs(scanlineDifference)) < Metrics::minDeltaForJitter || - !myJitterEnabled || - myTotalFrames < Metrics::initialGarbageFrames - ) return; - - myVblankManager.setJitter(scanlineDifference); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::updateAutodetectedLayout() -{ - if (myTotalFrames <= Metrics::initialGarbageFrames) { - return; - } - - const FrameLayout oldLayout = myLayout; - - const uInt32 - deltaNTSC = abs(Int32(myCurrentFrameFinalLines) - Int32(frameLinesNTSC)), - deltaPAL = abs(Int32(myCurrentFrameFinalLines) - Int32(frameLinesPAL)); - - if (std::min(deltaNTSC, deltaPAL) <= Metrics::tvModeDetectionTolerance) - updateLayout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal); - else if (!myModeConfirmed) { - if ( - (myCurrentFrameFinalLines < frameLinesPAL) && - (myCurrentFrameFinalLines > frameLinesNTSC) && - (myCurrentFrameFinalLines % 2) - ) - updateLayout(FrameLayout::ntsc); - else - updateLayout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal); - } - - if (oldLayout == myLayout) - myFramesInMode++; - else - myFramesInMode = 0; - - if (myFramesInMode > Metrics::framesForModeConfirmation) - myModeConfirmed = true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::updateLayout(FrameLayout layout) -{ - if (layout == myLayout) return; - -#ifdef TIA_FRAMEMANAGER_DEBUG_LOG - (cout << "TV mode switched to " << int(layout) << "\n").flush(); -#endif // TIA_FRAMEMANAGER_DEBUG_LOG - - myLayout = layout; - - switch (myLayout) - { - case FrameLayout::ntsc: - myVblankLines = Metrics::vblankNTSC; - myKernelLines = Metrics::kernelNTSC; - myOverscanLines = Metrics::overscanNTSC; - break; - - case FrameLayout::pal: - myVblankLines = Metrics::vblankPAL; - myKernelLines = Metrics::kernelPAL; - myOverscanLines = Metrics::overscanPAL; - break; - -// default: -// throw runtime_error("frame manager: invalid TV mode"); - } - - myFrameLines = Metrics::vsync + myVblankLines + myKernelLines + myOverscanLines; - if (myFixedHeight == 0) - myHeight = myKernelLines + Metrics::visibleOverscan; - - myVblankManager.setVblankLines(myVblankLines); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::setVblank(bool vblank) -{ - #ifdef TIA_FRAMEMANAGER_DEBUG_LOG - (cout << "vblank change " << myVblankManager.vblank() << " -> " << vblank << "@" << myLineInState << "\n").flush(); - #endif // TIA_FRAMEMANAGER_DEBUG_LOG - - if (myState == State::waitForFrameStart) { - if (myVblankManager.setVblankDuringVblank(vblank, myTotalFrames <= Metrics::initialGarbageFrames)) { - setState(State::frame); - } - } else - myVblankManager.setVblank(vblank); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::setFixedHeight(uInt32 height) -{ - myFixedHeight = height; - myHeight = myFixedHeight > 0 ? myFixedHeight : (myKernelLines + Metrics::visibleOverscan); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameManager::enableJitter(bool enabled) -{ - myJitterEnabled = enabled; - - if (!enabled) myVblankManager.setJitter(0); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool FrameManager::save(Serializer& out) const -{ - try - { - out.putString(name()); - - if (!myVblankManager.save(out)) return false; - - out.putInt(uInt32(myLayout)); - out.putBool(myAutodetectLayout); - out.putInt(uInt32(myState)); - out.putInt(myLineInState); - out.putInt(myCurrentFrameTotalLines); - out.putInt(myCurrentFrameFinalLines); - out.putInt(myPreviousFrameFinalLines); - out.putInt(myVsyncLines); - out.putDouble(myFrameRate); - out.putInt(myY); out.putInt(myLastY); - out.putBool(myFramePending); - - out.putInt(myTotalFrames); - out.putInt(myFramesInMode); - out.putBool(myModeConfirmed); - - out.putInt(myStableFrames); - out.putInt(myStabilizationFrames); - out.putBool(myHasStabilized); - - out.putBool(myVsync); - - out.putInt(myVblankLines); - out.putInt(myKernelLines); - out.putInt(myOverscanLines); - out.putInt(myFrameLines); - out.putInt(myHeight); - out.putInt(myFixedHeight); - - out.putBool(myJitterEnabled); - - out.putInt(myStableFrameLines); - out.putInt(myStableFrameHeightCountdown); - } - catch(...) - { - cerr << "ERROR: TIA_FrameManager::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool FrameManager::load(Serializer& in) -{ - try - { - if(in.getString() != name()) - return false; - - if (!myVblankManager.load(in)) return false; - - myLayout = FrameLayout(in.getInt()); - myAutodetectLayout = in.getBool(); - myState = State(in.getInt()); - myLineInState = in.getInt(); - myCurrentFrameTotalLines = in.getInt(); - myCurrentFrameFinalLines = in.getInt(); - myPreviousFrameFinalLines = in.getInt(); - myVsyncLines = in.getInt(); - myFrameRate = float(in.getDouble()); - myY = in.getInt(); myLastY = in.getInt(); - myFramePending = in.getBool(); - - myTotalFrames = in.getInt(); - myFramesInMode = in.getInt(); - myModeConfirmed = in.getBool(); - - myStableFrames = in.getInt(); - myStabilizationFrames = in.getInt(); - myHasStabilized = in.getBool(); - - myVsync = in.getBool(); - - myVblankLines = in.getInt(); - myKernelLines = in.getInt(); - myOverscanLines = in.getInt(); - myFrameLines = in.getInt(); - myHeight = in.getInt(); - myFixedHeight = in.getInt(); - - myJitterEnabled = in.getBool(); - - myStableFrameLines = in.getInt(); - myStableFrameHeightCountdown = in.getInt(); - } - catch(...) - { - cerr << "ERROR: TIA_FrameManager::load" << endl; - return false; - } - - return true; -} diff --git a/src/emucore/tia/FrameManager.hxx b/src/emucore/tia/FrameManager.hxx deleted file mode 100644 index 946216e93..000000000 --- a/src/emucore/tia/FrameManager.hxx +++ /dev/null @@ -1,188 +0,0 @@ -//============================================================================ -// -// 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_FRAME_MANAGER -#define TIA_FRAME_MANAGER - -#include - -#include "VblankManager.hxx" -#include "Serializable.hxx" -#include "FrameLayout.hxx" -#include "bspf.hxx" - -class FrameManager : public Serializable -{ - public: - - using callback = std::function; - - public: - - FrameManager(); - - public: - - static uInt8 initialGarbageFrames(); - - void setHandlers( - callback frameStartCallback, - callback frameCompletionCallback, - callback renderingStartCallback - ); - - void reset(); - - void nextLine(); - - void setVblank(bool vblank); - - void setVsync(bool vsync); - - bool isRendering() const { return myState == State::frame && myHasStabilized; } - - FrameLayout layout() const { return myLayout; } - - bool vblank() const { return myVblankManager.vblank(); } - - bool vsync() const { return myVsync; } - - uInt32 height() const { return myHeight; } - - void setFixedHeight(uInt32 height); - - uInt32 getY() const { return myY; } - - uInt32 scanlines() const { return myCurrentFrameTotalLines; } - - uInt32 scanlinesLastFrame() const { return myCurrentFrameFinalLines; } - - uInt32 missingScanlines() const; - - bool scanlineCountTransitioned() const { - return (myPreviousFrameFinalLines & 0x1) != (myCurrentFrameFinalLines & 0x1); - } - - uInt32 frameCount() const { return myTotalFrames; } - - float frameRate() const { return myFrameRate; } - - void setYstart(uInt32 ystart) { myVblankManager.setYstart(ystart); } - - uInt32 ystart() const { return myVblankManager.ystart(); } - bool ystartIsAuto(uInt32 line) const { return myVblankManager.ystartIsAuto(line); } - - void autodetectLayout(bool toggle) { myAutodetectLayout = toggle; } - - void setLayout(FrameLayout mode) { if (!myAutodetectLayout) updateLayout(mode); } - - /** - Serializable methods (see that class for more information). - */ - bool save(Serializer& out) const override; - bool load(Serializer& in) override; - string name() const override { return "TIA_FrameManager"; } - - void setJitterFactor(uInt8 factor) { myVblankManager.setJitterFactor(factor); } - bool jitterEnabled() const { return myJitterEnabled; } - void enableJitter(bool enabled); - - public: - static constexpr uInt32 frameBufferHeight = 320; - static constexpr uInt32 minYStart = 1, maxYStart = 64; - static constexpr uInt32 minViewableHeight = 210, maxViewableHeight = 256; - - private: - - enum State { - waitForVsyncStart, - waitForVsyncEnd, - waitForFrameStart, - frame - }; - - enum VblankMode { - locked, - floating, - final, - fixed - }; - - private: - - void updateLayout(FrameLayout mode); - - void updateAutodetectedLayout(); - - void setState(State state); - - void finalizeFrame(); - - void nextLineInVsync(); - - void handleJitter(Int32 scanlineDifference); - - private: - - callback myOnFrameStart; - callback myOnFrameComplete; - callback myOnRenderingStart; - - VblankManager myVblankManager; - - FrameLayout myLayout; - bool myAutodetectLayout; - State myState; - uInt32 myLineInState; - uInt32 myCurrentFrameTotalLines; - uInt32 myCurrentFrameFinalLines; - uInt32 myPreviousFrameFinalLines; - uInt32 myVsyncLines; - float myFrameRate; - uInt32 myY, myLastY; - bool myFramePending; - - uInt32 myTotalFrames; - uInt32 myFramesInMode; - bool myModeConfirmed; - - uInt32 myStableFrames; - uInt32 myStabilizationFrames; - bool myHasStabilized; - - bool myVsync; - - uInt32 myVblankLines; - uInt32 myKernelLines; - uInt32 myOverscanLines; - uInt32 myFrameLines; - uInt32 myHeight; - uInt32 myFixedHeight; - - bool myJitterEnabled; - - Int32 myStableFrameLines; - uInt8 myStableFrameHeightCountdown; - - private: - FrameManager(const FrameManager&) = delete; - FrameManager(FrameManager&&) = delete; - FrameManager& operator=(const FrameManager&) = delete; - FrameManager& operator=(FrameManager&&) = delete; -}; - -#endif // TIA_FRAME_MANAGER diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx index cb2c386da..c8bffee9f 100644 --- a/src/emucore/tia/TIA.cxx +++ b/src/emucore/tia/TIA.cxx @@ -21,6 +21,8 @@ #include "Control.hxx" #include "Paddles.hxx" #include "DelayQueueIteratorImpl.hxx" +#include "TIAConstants.hxx" +#include "frame-manager/FrameManager.hxx" #ifdef DEBUGGER_SUPPORT #include "CartDebug.hxx" @@ -67,6 +69,7 @@ TIA::TIA(Console& console, Sound& sound, Settings& settings) : myConsole(console), mySound(sound), mySettings(settings), + myFrameManager(nullptr), myPlayfield(~CollisionMask::playfield & 0x7FFF), myMissile0(~CollisionMask::missile0 & 0x7FFF), myMissile1(~CollisionMask::missile1 & 0x7FFF), @@ -76,18 +79,6 @@ TIA::TIA(Console& console, Sound& sound, Settings& settings) mySpriteEnabledBits(0xFF), myCollisionsEnabledBits(0xFF) { - myFrameManager.setHandlers( - [this] () { - onFrameStart(); - }, - [this] () { - onFrameComplete(); - }, - [this] () { - onRenderingStart(); - } - ); - myTIAPinsDriven = mySettings.getBool("tiadriven"); myBackground.setTIA(this); @@ -98,12 +89,42 @@ TIA::TIA(Console& console, Sound& sound, Settings& settings) myMissile1.setTIA(this); myBall.setTIA(this); - myFrameManager.enableJitter(mySettings.getBool("tv.jitter")); - myFrameManager.setJitterFactor(mySettings.getInt("tv.jitter_recovery")); + myEnableJitter = mySettings.getBool("tv.jitter"); + myJitterFactor = mySettings.getInt("tv.jitter_recovery"); reset(); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TIA::setFrameManager(AbstractFrameManager *frameManager) +{ + clearFrameManager(); + + myFrameManager = frameManager; + + myFrameManager->setHandlers( + [this] () { + onFrameStart(); + }, + [this] () { + onFrameComplete(); + } + ); + + myFrameManager->enableJitter(myEnableJitter); + myFrameManager->setJitterFactor(myJitterFactor); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TIA::clearFrameManager() +{ + if (!myFrameManager) return; + + myFrameManager->clearHandlers(); + + myFrameManager = nullptr; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::reset() { @@ -143,7 +164,8 @@ void TIA::reset() mySound.reset(); myDelayQueue.reset(); - myFrameManager.reset(); + + if (myFrameManager) myFrameManager->reset(); myCyclesAtFrameStart = 0; @@ -161,7 +183,7 @@ void TIA::reset() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::frameReset() { - memset(myFramebuffer, 0, 160 * FrameManager::frameBufferHeight); + memset(myFramebuffer, 0, 160 * TIAConstants::frameBufferHeight); myAutoFrameEnabled = mySettings.getInt("framerate") <= 0; enableColorLoss(mySettings.getBool("colorloss")); } @@ -205,7 +227,7 @@ bool TIA::save(Serializer& out) const if(!mySound.save(out)) return false; if(!myDelayQueue.save(out)) return false; - if(!myFrameManager.save(out)) return false; + if(!myFrameManager->save(out)) return false; if(!myBackground.save(out)) return false; if(!myPlayfield.save(out)) return false; @@ -276,7 +298,7 @@ bool TIA::load(Serializer& in) if(!mySound.load(in)) return false; if(!myDelayQueue.load(in)) return false; - if(!myFrameManager.load(in)) return false; + if(!myFrameManager->load(in)) return false; if(!myBackground.load(in)) return false; if(!myPlayfield.load(in)) return false; @@ -481,7 +503,7 @@ bool TIA::poke(uInt16 address, uInt8 value) break; case VSYNC: - myFrameManager.setVsync(value & 0x02); + myFrameManager->setVsync(value & 0x02); myShadowRegisters[address] = value; break; @@ -752,7 +774,7 @@ bool TIA::saveDisplay(Serializer& out) const { try { - out.putByteArray(myFramebuffer, 160*FrameManager::frameBufferHeight); + out.putByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight); } catch(...) { @@ -769,7 +791,7 @@ bool TIA::loadDisplay(Serializer& in) try { // Reset frame buffer pointer and data - in.getByteArray(myFramebuffer, 160*FrameManager::frameBufferHeight); + in.getByteArray(myFramebuffer, 160*TIAConstants::frameBufferHeight); } catch(...) { @@ -795,7 +817,7 @@ bool TIA::enableColorLoss(bool enabled) if(enabled) { myColorLossEnabled = true; - myColorLossActive = myFrameManager.scanlinesLastFrame() & 0x1; + myColorLossActive = myFrameManager->scanlinesLastFrame() & 0x1; } else { @@ -819,7 +841,7 @@ bool TIA::electronBeamPos(uInt32& x, uInt32& y) const uInt8 clocks = clocksThisLine(); x = (clocks < 68) ? 0 : clocks - 68; - y = myFrameManager.getY(); + y = myFrameManager->getY(); return isRendering(); } @@ -905,7 +927,11 @@ bool TIA::toggleCollisions() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool TIA::enableFixedColors(bool enable) { - int layout = myFrameManager.layout() == FrameLayout::pal ? 1 : 0; + // This will be called during reset at a point where no frame manager + // instance is available, so we guard aginst this here. + int layout = 0; + if (myFrameManager) layout = myFrameManager->layout() == FrameLayout::pal ? 1 : 0; + myMissile0.setDebugColor(myFixedColorPalette[layout][FixedObject::M0]); myMissile1.setDebugColor(myFixedColorPalette[layout][FixedObject::M1]); myPlayer0.setDebugColor(myFixedColorPalette[layout][FixedObject::P0]); @@ -990,22 +1016,32 @@ bool TIA::toggleJitter(uInt8 mode) { switch (mode) { case 0: - myFrameManager.enableJitter(false); + myEnableJitter = false; break; case 1: - myFrameManager.enableJitter(true); + myEnableJitter = true; break; case 2: - myFrameManager.enableJitter(!myFrameManager.jitterEnabled()); + myEnableJitter = !myEnableJitter; break; default: throw runtime_error("invalid argument for toggleJitter"); } - return myFrameManager.jitterEnabled(); + if (myFrameManager) myFrameManager->enableJitter(myEnableJitter); + + return myEnableJitter; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TIA::setJitterRecoveryFactor(Int32 factor) +{ + myJitterFactor = factor; + + if (myFrameManager) myFrameManager->setJitterFactor(myJitterFactor); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1080,9 +1116,9 @@ void TIA::onFrameStart() { // Only activate it when necessary, since changing colours in // the graphical object forces the TIA cached line to be flushed - if (myFrameManager.scanlineCountTransitioned()) + if (myFrameManager->scanlineCountTransitioned()) { - myColorLossActive = myFrameManager.scanlinesLastFrame() & 0x1; + myColorLossActive = myFrameManager->scanlinesLastFrame() & 0x1; myMissile0.applyColorLoss(); myMissile1.applyColorLoss(); @@ -1095,12 +1131,6 @@ void TIA::onFrameStart() } } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void TIA::onRenderingStart() -{ - myXAtRenderingStart = myHctr > 68 ? myHctr - 68 : 0; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::onFrameComplete() { @@ -1111,13 +1141,13 @@ void TIA::onFrameComplete() memset(myFramebuffer, 0, myXAtRenderingStart); // Blank out any extra lines not drawn this frame - const uInt32 missingScanlines = myFrameManager.missingScanlines(); + const Int32 missingScanlines = myFrameManager->missingScanlines(); if (missingScanlines > 0) - memset(myFramebuffer + 160 * myFrameManager.getY(), 0, missingScanlines * 160); + memset(myFramebuffer + 160 * myFrameManager->getY(), 0, missingScanlines * 160); // Recalculate framerate, attempting to auto-correct for scanline 'jumps' if(myAutoFrameEnabled) - myConsole.setFramerate(myFrameManager.frameRate()); + myConsole.setFramerate(myFrameManager->frameRate()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1147,7 +1177,7 @@ void TIA::cycle(uInt32 colorClocks) else tickHframe(); - if (myCollisionUpdateRequired && !myFrameManager.vblank()) updateCollision(); + if (myCollisionUpdateRequired && !myFrameManager->vblank()) updateCollision(); } if (++myHctr >= 228) @@ -1201,7 +1231,7 @@ void TIA::tickHblank() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::tickHframe() { - const uInt32 y = myFrameManager.getY(); + const uInt32 y = myFrameManager->getY(); const uInt32 x = myHctr - 68 - myHctrDelta; myCollisionUpdateRequired = true; @@ -1213,7 +1243,7 @@ void TIA::tickHframe() myPlayer1.tick(); myBall.tick(); - if (myFrameManager.isRendering()) + if (myFrameManager->isRendering()) renderPixel(x, y); } @@ -1223,8 +1253,8 @@ void TIA::applyRsync() const uInt32 x = myHctr > 68 ? myHctr - 68 : 0; myHctrDelta = 225 - myHctr; - if (myFrameManager.isRendering()) - memset(myFramebuffer + myFrameManager.getY() * 160 + x, 0, 160 - x); + if (myFrameManager->isRendering()) + memset(myFramebuffer + myFrameManager->getY() * 160 + x, 0, 160 - x); myHctr = 225; } @@ -1243,9 +1273,9 @@ void TIA::nextLine() myHstate = HState::blank; myHctrDelta = 0; - myFrameManager.nextLine(); + myFrameManager->nextLine(); - if (myFrameManager.isRendering() && myFrameManager.getY() == 0) flushLineCache(); + if (myFrameManager->isRendering() && myFrameManager->getY() == 0) flushLineCache(); mySystem->m6502().clearHaltRequest(); } @@ -1253,9 +1283,9 @@ void TIA::nextLine() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::cloneLastLine() { - const auto y = myFrameManager.getY(); + const auto y = myFrameManager->getY(); - if (!myFrameManager.isRendering() || y == 0) return; + if (!myFrameManager->isRendering() || y == 0) return; uInt8* buffer = myFramebuffer; @@ -1282,7 +1312,7 @@ void TIA::renderPixel(uInt32 x, uInt32 y) uInt8 color = 0; - if (!myFrameManager.vblank()) + if (!myFrameManager->vblank()) { switch (myPriority) { @@ -1355,8 +1385,8 @@ void TIA::flushLineCache() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIA::clearHmoveComb() { - if (myFrameManager.isRendering() && myHstate == HState::blank) - memset(myFramebuffer + myFrameManager.getY() * 160, myColorHBlank, 8); + if (myFrameManager->isRendering() && myHstate == HState::blank) + memset(myFramebuffer + myFrameManager->getY() * 160, myColorHBlank, 8); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1369,7 +1399,7 @@ void TIA::delayedWrite(uInt8 address, uInt8 value) { case VBLANK: flushLineCache(); - myFrameManager.setVblank(value & 0x02); + myFrameManager->setVblank(value & 0x02); break; case HMOVE: diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx index f1730179d..d764a4397 100644 --- a/src/emucore/tia/TIA.hxx +++ b/src/emucore/tia/TIA.hxx @@ -25,9 +25,10 @@ #include "Device.hxx" #include "Serializer.hxx" #include "TIATypes.hxx" +#include "TIAConstants.hxx" #include "DelayQueue.hxx" #include "DelayQueueIterator.hxx" -#include "FrameManager.hxx" +#include "frame-manager/AbstractFrameManager.hxx" #include "FrameLayout.hxx" #include "Background.hxx" #include "Playfield.hxx" @@ -100,9 +101,20 @@ class TIA : public Device @param settings The settings object for this TIA device */ TIA(Console& console, Sound& sound, Settings& settings); + virtual ~TIA() = default; public: + /** + * Configure the frame manager. + */ + void setFrameManager(AbstractFrameManager *frameManager); + + /** + * Clear the configured frame manager and deteach the lifecycle callbacks. + */ + void clearFrameManager(); + /** Reset device to its power-on state. */ @@ -192,21 +204,19 @@ class TIA : public Device Answers dimensional info about the framebuffer. */ uInt32 width() const { return 160; } - uInt32 height() const { return myFrameManager.height(); } - uInt32 ystart() const { return myFrameManager.ystart(); } - bool ystartIsAuto(uInt32 line) const { return myFrameManager.ystartIsAuto(line); } + uInt32 height() const { return myFrameManager->height(); } + uInt32 ystart() const { return myFrameManager->ystart(); } /** Changes the current Height/YStart properties. Note that calls to these method(s) must be eventually followed by ::frameReset() for the changes to take effect. */ - void setHeight(uInt32 height) { myFrameManager.setFixedHeight(height); } - void setYStart(uInt32 ystart) { myFrameManager.setYstart(ystart); } + void setHeight(uInt32 height) { myFrameManager->setFixedHeight(height); } + void setYStart(uInt32 ystart) { myFrameManager->setYstart(ystart); } - void autodetectLayout(bool toggle) { myFrameManager.autodetectLayout(toggle); } - void setLayout(FrameLayout layout) { myFrameManager.setLayout(layout); } - FrameLayout frameLayout() const { return myFrameManager.layout(); } + void setLayout(FrameLayout layout) { myFrameManager->setLayout(layout); } + FrameLayout frameLayout() const { return myFrameManager->layout(); } /** Answers the timing of the console currently in use. @@ -249,7 +259,7 @@ class TIA : public Device @return The total number of scanlines generated */ - uInt32 scanlines() const { return myFrameManager.scanlines(); } + uInt32 scanlines() const { return myFrameManager->scanlines(); } /** Answers the total number of scanlines the TIA generated in the @@ -257,7 +267,7 @@ class TIA : public Device @return The total number of scanlines generated in the last frame. */ - uInt32 scanlinesLastFrame() const { return myFrameManager.scanlinesLastFrame(); } + uInt32 scanlinesLastFrame() const { return myFrameManager->scanlinesLastFrame(); } /** Answers the total system cycles from the start of the emulation. @@ -267,7 +277,7 @@ class TIA : public Device /** Answers the frame count from the start of the emulation. */ - uInt32 frameCount() const { return myFrameManager.frameCount(); } + uInt32 frameCount() const { return myFrameManager->frameCount(); } /** Answers the system cycles from the start of the current frame. @@ -282,7 +292,7 @@ class TIA : public Device @return If the frame is in rendering mode */ - bool isRendering() const { return myFrameManager.isRendering(); } + bool isRendering() const { return myFrameManager->isRendering(); } /** Answers the current position of the virtual 'electron beam' used @@ -359,7 +369,7 @@ class TIA : public Device @return Whether the mode was enabled or disabled */ bool toggleJitter(uInt8 mode = 2); - void setJitterRecoveryFactor(Int32 factor) { myFrameManager.setJitterFactor(factor); } + void setJitterRecoveryFactor(Int32 factor); /** This method should be called to update the TIA with a new scanline. @@ -451,12 +461,6 @@ class TIA : public Device */ void onFrameStart(); - /** - * This callback is invoked by FrameManager when the visible range of the - * current frame starts. - */ - void onRenderingStart(); - /** * This callback is invoked by FrameManager when the current frame completes. */ @@ -598,7 +602,7 @@ class TIA : public Device * The frame manager is responsible for detecting frame boundaries and the visible * region of each frame. */ - FrameManager myFrameManager; + AbstractFrameManager *myFrameManager; /** * The various TIA objects. @@ -622,10 +626,8 @@ class TIA : public Device LatchedInput myInput0; LatchedInput myInput1; - /** - * Pointer to the internal color-index-based frame buffer - */ - uInt8 myFramebuffer[160 * FrameManager::frameBufferHeight]; + // Pointer to the internal color-index-based frame buffer + uInt8 myFramebuffer[160 * TIAConstants::frameBufferHeight]; /** * Setting this to true injects random values into undefined reads. @@ -738,10 +740,17 @@ class TIA : public Device bool myColorLossActive; /** - * System cycles at the end of the previous frame / beginning of next frame + * System cycles at the end of the previous frame / beginning of next frame. */ uInt64 myCyclesAtFrameStart; + /** + * The frame manager can change during our lifetime, so we buffer those two. + */ + bool myEnableJitter; + uInt8 myJitterFactor; + + #ifdef DEBUGGER_SUPPORT // The arrays containing information about every byte of TIA // indicating whether and how (RW) it is used. diff --git a/src/emucore/tia/TIAConstants.hxx b/src/emucore/tia/TIAConstants.hxx new file mode 100644 index 000000000..ec7bc5a88 --- /dev/null +++ b/src/emucore/tia/TIAConstants.hxx @@ -0,0 +1,15 @@ +#ifndef TIA_CONSTANTS_HXX +#define TIA_CONSTANTS_HXX + +#include "bspf.hxx" + +namespace TIAConstants { + + constexpr uInt32 frameBufferHeight = 320; + constexpr uInt32 minYStart = 1, maxYStart = 64; + constexpr uInt32 minViewableHeight = 210, maxViewableHeight = 256; + constexpr uInt32 initialGarbageFrames = 10; + +} + +#endif // TIA_CONSTANTS_HXX diff --git a/src/emucore/tia/VblankManager.cxx b/src/emucore/tia/VblankManager.cxx deleted file mode 100644 index aacb2c1c8..000000000 --- a/src/emucore/tia/VblankManager.cxx +++ /dev/null @@ -1,306 +0,0 @@ -//============================================================================ -// -// 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. -//============================================================================ - -// #define TIA_VBLANK_MANAGER_DEBUG_LOG - -#include - -#include "VblankManager.hxx" - -enum Metrics: uInt32 { - maxUnderscan = 10, - maxVblankViolations = 2, - minStableVblankFrames = 1, - framesUntilFinal = 30, - maxJitter = 50 -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -VblankManager::VblankManager() - : myVblankLines(0), - myYstart(0), - myMode(VblankMode::floating), - myJitter(0), - myJitterFactor(2) -{ - reset(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void VblankManager::reset() -{ - myVblank = false; - myCurrentLine = 0; - myVblankViolations = 0; - myStableVblankFrames = 0; - myVblankViolated = false; - myLastVblankLines = 0; - myIsRunning = false; - myJitter = 0; - - if (myMode != VblankMode::fixed) setVblankMode(VblankMode::floating); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void VblankManager::setJitter(Int32 jitter) { - jitter = std::min(jitter, Metrics::maxJitter); - - if (myMode == VblankMode::final) jitter = std::max(jitter, -myLastVblankLines); - if (myMode == VblankMode::fixed) jitter = std::max(jitter, -myYstart); - - if (jitter > 0) jitter += myJitterFactor; - if (jitter < 0) jitter -= myJitterFactor; - - if (abs(jitter) > abs(myJitter)) myJitter = jitter; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void VblankManager::start() -{ - myCurrentLine = 0; - myIsRunning = true; - myVblankViolated = false; - - if (myJitter > 0) myJitter = std::max(myJitter - myJitterFactor, 0); - if (myJitter < 0) myJitter = std::min(myJitter + myJitterFactor, 0); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool VblankManager::nextLine(bool isGarbageFrame) -{ - if (!myIsRunning) return false; - - // Make sure that we do the transition check **before** incrementing the line - // counter. This ensures that, if the transition is caused by VBLANK off during - // the line, this will continue to trigger the transition in 'locked' mode. Otherwise, - // the transition would be triggered by the line change **before** the VBLANK - // and thus detected as a suprious violation. Sigh, this stuff is complicated, - // isn't it? - const bool transition = shouldTransition(isGarbageFrame); - if (transition) myIsRunning = false; - - myCurrentLine++; - - return transition; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void VblankManager::setYstart(uInt32 ystart) -{ - if (ystart == myYstart) return; - - myYstart = ystart; - - setVblankMode(ystart ? VblankMode::fixed : VblankMode::floating); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool VblankManager::setVblankDuringVblank(bool vblank, bool isGarbageFrame) -{ - const bool oldVblank = myVblank; - - myVblank = vblank; - if (!myIsRunning || vblank || oldVblank == myVblank) return false; - - const bool transition = shouldTransition(isGarbageFrame); - if (transition) myIsRunning = false; - - return transition; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool VblankManager::shouldTransition(bool isGarbageFrame) -{ - // Are we free to transition as per vblank cycle? - bool shouldTransition = myCurrentLine + 1 >= (myVblank ? myVblankLines : myVblankLines - Metrics::maxUnderscan); - - // Do we **actually** transition? This depends on what mode we are in. - bool transition = false; - - switch (myMode) { - // 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 (!isGarbageFrame && myCurrentLine == 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 = myCurrentLine; - -#ifdef TIA_VBLANK_MANAGER_DEBUG_LOG - (cout << "leaving vblank in floating mode, should transition: " << shouldTransition << "\n").flush(); -#endif - // 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) { - setVblankMode(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 (myCurrentLine == myLastVblankLines) { - - // Are we free to transition per the algorithm and didn't we 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; - } - -#ifdef TIA_VBLANK_MANAGER_DEBUG_LOG - (cout << "leaving vblank in locked mode, should transition: " << shouldTransition << "\n").flush(); -#endif - - // 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; - } - - // Freeze frame start if the detected value seems to be sufficiently stable - if (transition && ++myFramesInLockedMode > Metrics::framesUntilFinal) { - setVblankMode(VblankMode::final); - // Revert to floating mode if there were too many irregular frames in a row - } else if (myVblankViolations > Metrics::maxVblankViolations) { - setVblankMode(VblankMode::floating); - myStableVblankFrames = 0; - } - - break; - - // Fixed mode: use external ystart value - case VblankMode::fixed: - transition = Int32(myCurrentLine) >= - std::max(myYstart + std::min(myJitter, Metrics::maxJitter), 0); - break; - - // Final mode: use detected ystart value - case VblankMode::final: - transition = Int32(myCurrentLine) >= - std::max(myLastVblankLines + std::min(myJitter, Metrics::maxJitter), 0); - break; - } - - return transition; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void VblankManager::setVblankMode(VblankMode mode) -{ - if (myMode == mode) return; - - myMode = mode; - - switch (myMode) { - case VblankMode::locked: - myFramesInLockedMode = 0; - break; - - default: - break; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool VblankManager::save(Serializer& out) const -{ - try - { - out.putString(name()); - - out.putInt(myVblankLines); - out.putInt(myYstart); - out.putBool(myVblank); - out.putInt(myCurrentLine); - - out.putInt(int(myMode)); - out.putInt(myLastVblankLines); - out.putByte(myVblankViolations); - out.putByte(myStableVblankFrames); - out.putBool(myVblankViolated); - out.putByte(myFramesInLockedMode); - - out.putInt(myJitter); - out.putByte(myJitterFactor); - - out.putBool(myIsRunning); - } - catch(...) - { - cerr << "ERROR: TIA_VblankManager::save" << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool VblankManager::load(Serializer& in) -{ - try - { - if(in.getString() != name()) - return false; - - myVblankLines = in.getInt(); - myYstart = in.getInt(); - myVblank = in.getBool(); - myCurrentLine = in.getInt(); - - myMode = VblankMode(in.getInt()); - myLastVblankLines = in.getInt(); - myVblankViolations = in.getByte(); - myStableVblankFrames = in.getByte(); - myVblankViolated = in.getBool(); - myFramesInLockedMode = in.getByte(); - - myJitter = in.getInt(); - myJitterFactor = in.getByte(); - - myIsRunning = in.getBool(); - } - catch(...) - { - cerr << "ERROR: TIA_VblankManager::load" << endl; - return false; - } - - return true; -} diff --git a/src/emucore/tia/VblankManager.hxx b/src/emucore/tia/VblankManager.hxx deleted file mode 100644 index c8632f237..000000000 --- a/src/emucore/tia/VblankManager.hxx +++ /dev/null @@ -1,107 +0,0 @@ -//============================================================================ -// -// 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_VBLANK_MANAGER -#define TIA_VBLANK_MANAGER - -#include "Serializable.hxx" - -class VblankManager : public Serializable -{ - public: - - VblankManager(); - - public: - - void reset(); - - void start(); - - bool nextLine(bool isGarbageFrame); - - void setVblankLines(uInt32 lines) { myVblankLines = lines; } - - void setYstart(uInt32 ystart); - - uInt32 ystart() const { return myYstart == 0 ? myLastVblankLines : myYstart; } - bool ystartIsAuto(uInt32 line) const { return myLastVblankLines == line; } - - void setVblank(bool vblank) { myVblank = vblank; } - - bool setVblankDuringVblank(bool vblank, bool isGarbageFrame); - - bool vblank() const { return myVblank; } - - uInt32 currentLine() const { return myCurrentLine; } - - void setJitter(Int32 jitter); - void setJitterFactor(uInt8 jitterFactor) { myJitterFactor = jitterFactor; } - - bool isStable() const { return myMode != VblankMode::floating; } - - /** - Serializable methods (see that class for more information). - */ - bool save(Serializer& out) const override; - bool load(Serializer& in) override; - string name() const override { return "TIA_VblankManager"; } - - private: - - enum VblankMode { - locked, - floating, - final, - fixed - }; - - private: - - bool shouldTransition(bool isGarbageFrame); - - void setVblankMode(VblankMode mode); - - private: - - uInt32 myVblankLines; - uInt32 myYstart; - bool myVblank; - uInt32 myCurrentLine; - - VblankMode myMode; - uInt32 myLastVblankLines; - uInt8 myVblankViolations; - uInt8 myStableVblankFrames; - bool myVblankViolated; - uInt8 myFramesInLockedMode; - - Int32 myJitter; - uInt8 myJitterFactor; - - bool myIsRunning; - - private: - - VblankManager(const VblankManager&) = delete; - VblankManager(VblankManager&&) = delete; - VblankManager& operator=(const VblankManager&) = delete; - VblankManager& operator=(VblankManager&&) = delete; - -}; - -#endif // TIA_VBLANK_MANAGER diff --git a/src/emucore/tia/frame-manager/AbstractFrameManager.cxx b/src/emucore/tia/frame-manager/AbstractFrameManager.cxx new file mode 100644 index 000000000..254473ff0 --- /dev/null +++ b/src/emucore/tia/frame-manager/AbstractFrameManager.cxx @@ -0,0 +1,165 @@ +//============================================================================ +// +// 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 "AbstractFrameManager.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AbstractFrameManager::AbstractFrameManager() : + myLayout(FrameLayout::pal), + myOnFrameStart(nullptr), + myOnFrameComplete(nullptr) +{ + layout(FrameLayout::ntsc); + reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::reset() +{ + myIsRendering = false; + myVsync = false; + myVblank = false; + myCurrentFrameTotalLines = 0; + myCurrentFrameFinalLines = 0; + myPreviousFrameFinalLines = 0; + myTotalFrames = 0; + myFrameRate = 0; + myFrameRate = 60.0; + + onReset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::nextLine() +{ + myCurrentFrameTotalLines++; + + onNextLine(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::setHandlers( + callback frameStartCallback, + callback frameCompletionCallback +) { + myOnFrameStart = frameStartCallback; + myOnFrameComplete = frameCompletionCallback; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::clearHandlers() +{ + myOnFrameStart = myOnFrameComplete = nullptr; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::setVblank(bool vblank) +{ + if (vblank == myVblank) return; + + myVblank = vblank; + + onSetVblank(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::setVsync(bool vsync) +{ + if (vsync == myVsync) return; + + myVsync = vsync; + + onSetVsync(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::notifyFrameStart() +{ + if (myOnFrameStart) myOnFrameStart(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::notifyFrameComplete() +{ + myPreviousFrameFinalLines = myCurrentFrameFinalLines; + myCurrentFrameFinalLines = myCurrentFrameTotalLines; + myCurrentFrameTotalLines = 0; + myTotalFrames++; + + if (myOnFrameComplete) myOnFrameComplete(); + + myFrameRate = (layout() == FrameLayout::pal ? 15600.0 : 15720.0) / + myCurrentFrameFinalLines; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void AbstractFrameManager::layout(FrameLayout layout) +{ + if (layout == myLayout) return; + + myLayout = layout; + + onLayoutChange(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AbstractFrameManager::save(Serializer& out) const +{ + try { + out.putString(name()); + + out.putBool(myIsRendering); + out.putBool(myVsync); + out.putBool(myVblank); + out.putInt(myCurrentFrameFinalLines); + out.putInt(myPreviousFrameFinalLines); + out.putInt(myTotalFrames); + out.putInt(uInt32(myLayout)); + out.putDouble(myFrameRate); + + return onSave(out); + } + catch(...) + { + cerr << "ERROR: AbstractFrameManager::save" << endl; + return false; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AbstractFrameManager::load(Serializer& in) +{ + try { + if (in.getString() != name()) return false; + + myIsRendering = in.getBool(); + myVsync = in.getBool(); + myVblank = in.getBool(); + myCurrentFrameFinalLines = in.getInt(); + myPreviousFrameFinalLines = in.getInt(); + myTotalFrames = in.getInt(); + myLayout = FrameLayout(in.getInt()); + myFrameRate = float(in.getDouble()); + + return onLoad(in); + } + catch(...) + { + cerr << "ERROR: AbstractFrameManager::load" << endl; + return false; + } +} diff --git a/src/emucore/tia/frame-manager/AbstractFrameManager.hxx b/src/emucore/tia/frame-manager/AbstractFrameManager.hxx new file mode 100644 index 000000000..ccb655ab6 --- /dev/null +++ b/src/emucore/tia/frame-manager/AbstractFrameManager.hxx @@ -0,0 +1,321 @@ +//============================================================================ +// +// 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_ABSTRACT_FRAME_MANAGER +#define TIA_ABSTRACT_FRAME_MANAGER + +#include + +#include "Serializable.hxx" +#include "FrameLayout.hxx" + +class AbstractFrameManager : public Serializable +{ + public: + + using callback = std::function; + + public: + + AbstractFrameManager(); + + public: + + /** + * Configure the various handler callbacks. + */ + void setHandlers( + callback frameStartCallback, + callback frameCompletionCallback + ); + + /** + * Clear the configured handler callbacks. + */ + void clearHandlers(); + + /** + * Reset. + */ + void reset(); + + /** + * Called by TIA to notify the start of the next scanline. + */ + void nextLine(); + + /** + * Called by TIA on VBLANK writes. + */ + void setVblank(bool vblank); + + /** + * Called by TIA on VSYNC writes. + */ + void setVsync(bool vsync); + + /** + * Should the TIA render its frame? This is buffered in a flag for + * performance reasons; descendants must update the flag. + */ + bool isRendering() const { return myIsRendering; } + + /** + * Is vsync on? + */ + bool vsync() const { return myVsync; } + + /** + * Is vblank on? + */ + bool vblank() const { return myVblank; } + + /** + * The number of scanlines in the last finished frame. + */ + uInt32 scanlinesLastFrame() const { return myCurrentFrameFinalLines; } + + /** + * Did the number of scanlines switch between even / odd (used for color loss + * emulation). + * + * TODO: Crappy name, find something better. + */ + bool scanlineCountTransitioned() const { + return (myPreviousFrameFinalLines & 0x1) != (myCurrentFrameFinalLines & 0x1); + } + + /** + * The total number of frames. 32 bit should be good for > 2 years :) + */ + uInt32 frameCount() const { return myTotalFrames; } + + /** + * The configured (our autodetected) frame layout (PAL / NTSC). + */ + FrameLayout layout() const { return myLayout; } + + /** + * The current frame rate. This is calculated dynamically from the number of + * scanlines in the last frames and used to control sleep time in the + * dispatch loop. + */ + float frameRate() const { return myFrameRate; } + + /** + * Save state. + */ + bool save(Serializer& out) const override; + + /** + * Restore state. + */ + bool load(Serializer& in) override; + + public: + // The following methods are implement as noops and should be overriden as + // required. All of these are irrelevant if nothing is displayed (during + // autodetect). + + /** + * The jitter factor determines the time jitter simulation takes to recover. + */ + virtual void setJitterFactor(uInt8 factor) {} + + /** + * Is jitter simulation enabled? + */ + virtual bool jitterEnabled() const { return false; } + + /** + * Enable jitter simulation + */ + virtual void enableJitter(bool enabled) {} + + /** + * The scanline difference between the last two frames. Used in the TIA to + * clear any scanlines that were not repainted. + */ + virtual Int32 missingScanlines() const { return 0; } + + /** + * Frame height. + */ + virtual uInt32 height() const { return 0; } + + /** + * Configure a fixed frame height (the default is determined by the frame + * layout). + */ + virtual void setFixedHeight(uInt32 height) {} + + /** + * The current y coordinate (valid only during rendering). + */ + virtual uInt32 getY() const { return 0; } + + /** + * The current number of scanlines in the current frame (including invisible + * lines). + */ + virtual uInt32 scanlines() const { return 0; } + + /** + * Configure the ystart value. + */ + virtual void setYstart(uInt32 ystart) {} + + /** + * The configured ystart value. + */ + virtual uInt32 ystart() const { return 0; } + + /** + * Set the frame layout. This may be a noop (on the autodetection manager). + */ + virtual void setLayout(FrameLayout mode) {} + + protected: + // The following are template methods that can be implemented to hook into + // the frame logic. + + /** + * Called if vblank changes. + */ + virtual void onSetVblank() {} + + /** + * Called if vsync changes. + */ + virtual void onSetVsync() {} + + /** + * Called if the next line is signalled, after the internal bookkeeping has + * been updated. + */ + virtual void onNextLine() {} + + /** + * Called on reset (after the base class has reset). + */ + virtual void onReset() {} + + /** + * Called after a frame layout change. + */ + virtual void onLayoutChange() {} + + /** + * Called during state save (after the base class has serialized its state). + */ + virtual bool onSave(Serializer& out) const { throw runtime_error("cannot be serialized"); } + + /** + * Called during state restore (after the base class has restored its state). + */ + virtual bool onLoad(Serializer& in) { throw runtime_error("cannot be serialized"); } + + /** + * This needs to be overriden if state serialization is implemented + * (unnecesary in autodetect managers). + */ + string name() const override { throw runtime_error("state serialization is not implemented!"); } + + protected: + // These need to be called in order to drive the frame lifecycle of the + // emulation. + + /** + * Signal frame start. + */ + void notifyFrameStart(); + + /** + * Signal frame stop. + */ + void notifyFrameComplete(); + + /** + * The internal setter to update the frame layout. + */ + void layout(FrameLayout layout); + + protected: + + /** + * Rendering flag. + */ + bool myIsRendering; + + /** + * Vsync flag. + */ + bool myVsync; + + /** + * Vblank flag. + */ + bool myVblank; + + /** + * Current scanline count in the current frame. + */ + uInt32 myCurrentFrameTotalLines; + + /** + * Total number of scanlines in the last complete frame. + */ + uInt32 myCurrentFrameFinalLines; + + /** + * Total number of scanlines in the second last complete frame. + */ + uInt32 myPreviousFrameFinalLines; + + /** + * Total frame count. + */ + uInt32 myTotalFrames; + + /** + * Frame rate (see above.) + */ + float myFrameRate; + + private: + + /** + * Current frame layout. + */ + FrameLayout myLayout; + + /** + * The various lifecycle callbacks. + */ + callback myOnFrameStart; + callback myOnFrameComplete; + + private: + + AbstractFrameManager(const AbstractFrameManager&) = delete; + AbstractFrameManager(AbstractFrameManager&&) = delete; + AbstractFrameManager& operator=(const AbstractFrameManager&); + AbstractFrameManager& operator=(AbstractFrameManager&&); + +}; + +#endif // TIA_ABSTRACT_FRAME_MANAGER diff --git a/src/emucore/tia/frame-manager/FrameLayoutDetector.cxx b/src/emucore/tia/frame-manager/FrameLayoutDetector.cxx new file mode 100644 index 000000000..9538201c6 --- /dev/null +++ b/src/emucore/tia/frame-manager/FrameLayoutDetector.cxx @@ -0,0 +1,143 @@ +//============================================================================ +// +// 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 "FrameLayoutDetector.hxx" +#include "TIAConstants.hxx" + +/** + * Misc. numeric constants used in the algorithm. + */ +enum Metrics: uInt32 { + frameLinesNTSC = 262, + frameLinesPAL = 312, + waitForVsync = 100, + tvModeDetectionTolerance = 20, + initialGarbageFrames = TIAConstants::initialGarbageFrames +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameLayout FrameLayoutDetector::detectedLayout() const{ + // We choose the mode that was detected for the majority of frames. + return myPalFrames > myNtscFrames ? FrameLayout::pal : FrameLayout::ntsc; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameLayoutDetector::onReset() +{ + myState = State::waitForVsyncStart; + myNtscFrames = myPalFrames = 0; + myLinesWaitingForVsyncToStart = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameLayoutDetector::onSetVsync() +{ + if (myVsync) + setState(State::waitForVsyncEnd); + else + setState(State::waitForVsyncStart); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameLayoutDetector::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::waitForVsyncStart); + + break; + + default: + throw runtime_error("cannot happen"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameLayoutDetector::setState(State state) +{ + if (state == myState) return; + + myState = state; + myLinesWaitingForVsyncToStart = 0; + + switch (myState) { + case State::waitForVsyncEnd: + break; + + case State::waitForVsyncStart: + finalizeFrame(); + notifyFrameStart(); + break; + + default: + throw new runtime_error("cannot happen"); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameLayoutDetector::finalizeFrame() +{ + notifyFrameComplete(); + + if (myTotalFrames <= Metrics::initialGarbageFrames) return; + + // Calculate the delta between scanline count and the sweet spot for the respective + // frame layouts + const uInt32 + deltaNTSC = abs(Int32(myCurrentFrameFinalLines) - Int32(frameLinesNTSC)), + deltaPAL = abs(Int32(myCurrentFrameFinalLines) - Int32(frameLinesPAL)); + + // Does the scanline count fall into one of our tolerance windows? -> use it + if (std::min(deltaNTSC, deltaPAL) <= Metrics::tvModeDetectionTolerance) + layout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal); + else if ( + // If scanline count is odd and lies between the PAL and NTSC windows we assume + // it is NTSC (it would cause color loss on PAL CRTs) + (myCurrentFrameFinalLines < frameLinesPAL) && + (myCurrentFrameFinalLines > frameLinesNTSC) && + (myCurrentFrameFinalLines % 2) + ) + layout(FrameLayout::ntsc); + else + // Take the nearest layout if all else fails + layout(deltaNTSC <= deltaPAL ? FrameLayout::ntsc : FrameLayout::pal); + + switch (layout()) { + case FrameLayout::ntsc: + myNtscFrames++; + break; + + case FrameLayout::pal: + myPalFrames++; + break; + + default: + throw runtime_error("cannot happen"); + } +} diff --git a/src/emucore/tia/frame-manager/FrameLayoutDetector.hxx b/src/emucore/tia/frame-manager/FrameLayoutDetector.hxx new file mode 100644 index 000000000..e3117ffd8 --- /dev/null +++ b/src/emucore/tia/frame-manager/FrameLayoutDetector.hxx @@ -0,0 +1,110 @@ +//============================================================================ +// +// 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_FRAME_LAYOUT_DETECTOR +#define TIA_FRAME_LAYOUT_DETECTOR + +#include "AbstractFrameManager.hxx" +#include "FrameLayout.hxx" + +/** + * This frame manager performs frame layout autodetection. It counts the scanlines + * in each frame and assigns guesses the frame layout from this. + */ +class FrameLayoutDetector: public AbstractFrameManager { + public: + + FrameLayoutDetector() = default; + + public: + + /** + * Return the detected frame layout. + */ + FrameLayout detectedLayout() const; + + protected: + + /** + * Hook into vsync changes. + */ + void onSetVsync() override; + + /** + * Hook into reset. + */ + void onReset() override; + + /** + * Hook into line changes. + */ + void onNextLine() override; + + private: + + /** + * This frame manager only tracks frame boundaries, so we have only two states. + */ + enum State { + // Wait for VSYNC to be enabled. + waitForVsyncStart, + + // Wait for VSYNC to be disabled. + waitForVsyncEnd + }; + + + private: + + /** + * Change state and change internal state accordingly. + */ + void setState(State state); + + /** + * Finalize the current frame and guess frame layout from the scanline count. + */ + void finalizeFrame(); + + private: + + /** + * The current state. + */ + State myState; + + /** + * The total number of frames detected as the respective frame layout. + */ + uInt32 myNtscFrames, myPalFrames; + + /** + * We count the number of scanlines we spend waiting for vsync to be + * toggled. If a threshold is exceeded, we force the transition. + */ + uInt32 myLinesWaitingForVsyncToStart; + + private: + + FrameLayoutDetector(const FrameLayoutDetector&) = delete; + FrameLayoutDetector(FrameLayoutDetector&&) = delete; + FrameLayoutDetector& operator=(const FrameLayoutDetector&) = delete; + FrameLayoutDetector& operator=(FrameLayoutDetector&&) = delete; + +}; + +#endif // TIA_FRAME_LAYOUT_DETECTOR diff --git a/src/emucore/tia/frame-manager/FrameManager.cxx b/src/emucore/tia/frame-manager/FrameManager.cxx new file mode 100644 index 000000000..40629f152 --- /dev/null +++ b/src/emucore/tia/frame-manager/FrameManager.cxx @@ -0,0 +1,253 @@ +//============================================================================ +// +// 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. +//============================================================================ + +// #define TIA_FRAMEMANAGER_DEBUG_LOG + +#include + +#include "FrameManager.hxx" + +enum Metrics: uInt32 { + vblankNTSC = 37, + vblankPAL = 45, + kernelNTSC = 192, + kernelPAL = 228, + overscanNTSC = 30, + overscanPAL = 36, + vsync = 3, + maxLinesVsync = 32, + visibleOverscan = 20, + tvModeDetectionTolerance = 20, + initialGarbageFrames = TIAConstants::initialGarbageFrames, + minStableFrames = 10, + maxStabilizationFrames = 20, + minDeltaForJitter = 3, + framesForStableHeight = 2 +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameManager::FrameManager() : + myHeight(0), + myYStart(0) +{ + onLayoutChange(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameManager::onReset() +{ + myState = State::waitForVsyncStart; + myLineInState = 0; + myTotalFrames = 0; + myVsyncLines = 0; + myY = 0; + + myStableFrameLines = -1; + myStableFrameHeightCountdown = 0; + + myJitterEmulation.reset(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameManager::onNextLine() +{ + Int32 jitter; + + State previousState = myState; + myLineInState++; + + switch (myState) + { + case State::waitForVsyncStart: + if ((myCurrentFrameTotalLines > myFrameLines - 3) || myTotalFrames == 0) + myVsyncLines++; + + if (myVsyncLines > Metrics::maxLinesVsync) setState(State::waitForFrameStart); + + break; + + case State::waitForVsyncEnd: + if (++myVsyncLines > Metrics::maxLinesVsync) + setState(State::waitForFrameStart); + + break; + + case State::waitForFrameStart: + jitter = + (myJitterEnabled && myTotalFrames > Metrics::initialGarbageFrames) ? myJitterEmulation.jitter() : 0; + + if (myLineInState >= (myYStart + jitter)) setState(State::frame); + break; + + case State::frame: + if (myLineInState >= myHeight) + { + myLastY = ystart() + myY; // Last line drawn in this frame + setState(State::waitForVsyncStart); + } + break; + + default: + throw runtime_error("frame manager: invalid state"); + } + + if (myState == State::frame && previousState == State::frame) myY++; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int32 FrameManager::missingScanlines() const +{ + if (myLastY == myYStart + myY) + return 0; + else { + return myHeight - myY; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameManager::setYstart(uInt32 ystart) +{ + myYStart = ystart; + myJitterEmulation.setYStart(ystart); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameManager::onSetVsync() +{ + if (myState == State::waitForVsyncEnd) setState(State::waitForFrameStart); + else setState(State::waitForVsyncEnd); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameManager::setState(FrameManager::State state) +{ + if (myState == state) return; + + myState = state; + myLineInState = 0; + + switch (myState) { + case State::waitForFrameStart: + notifyFrameComplete(); + myJitterEmulation.frameComplete(myCurrentFrameFinalLines); + notifyFrameStart(); + + myVsyncLines = 0; + break; + + case State::frame: + myVsyncLines = 0; + myY = 0; + break; + + default: + break; + } + + updateIsRendering(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// TODO: kill this with fire once frame manager refactoring is complete +void FrameManager::onLayoutChange() +{ + switch (layout()) + { + case FrameLayout::ntsc: + myVblankLines = Metrics::vblankNTSC; + myKernelLines = Metrics::kernelNTSC; + myOverscanLines = Metrics::overscanNTSC; + break; + + case FrameLayout::pal: + myVblankLines = Metrics::vblankPAL; + myKernelLines = Metrics::kernelPAL; + myOverscanLines = Metrics::overscanPAL; + break; + + default: + throw runtime_error("frame manager: invalid TV mode"); + } + + myFrameLines = Metrics::vsync + myVblankLines + myKernelLines + myOverscanLines; + if (myFixedHeight == 0) + myHeight = myKernelLines + Metrics::visibleOverscan; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameManager::setFixedHeight(uInt32 height) +{ + myFixedHeight = height; + myHeight = myFixedHeight > 0 ? myFixedHeight : (myKernelLines + Metrics::visibleOverscan); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameManager::updateIsRendering() { + myIsRendering = myState == State::frame; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameManager::onSave(Serializer& out) const +{ + if (!myJitterEmulation.save(out)) return false; + + out.putInt(uInt32(myState)); + out.putInt(myLineInState); + out.putInt(myVsyncLines); + out.putInt(myY); + out.putInt(myLastY); + + out.putInt(myVblankLines); + out.putInt(myKernelLines); + out.putInt(myOverscanLines); + out.putInt(myFrameLines); + out.putInt(myHeight); + out.putInt(myFixedHeight); + + out.putBool(myJitterEnabled); + + out.putInt(myStableFrameLines); + out.putInt(myStableFrameHeightCountdown); + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameManager::onLoad(Serializer& in) +{ + if (!myJitterEmulation.load(in)) return false; + + myState = State(in.getInt()); + myLineInState = in.getInt(); + myVsyncLines = in.getInt(); + myY = in.getInt(); + myLastY = in.getInt(); + + myVblankLines = in.getInt(); + myKernelLines = in.getInt(); + myOverscanLines = in.getInt(); + myFrameLines = in.getInt(); + myHeight = in.getInt(); + myFixedHeight = in.getInt(); + + myJitterEnabled = in.getBool(); + + myStableFrameLines = in.getInt(); + myStableFrameHeightCountdown = in.getInt(); + + return true; +} diff --git a/src/emucore/tia/frame-manager/FrameManager.hxx b/src/emucore/tia/frame-manager/FrameManager.hxx new file mode 100644 index 000000000..7df958460 --- /dev/null +++ b/src/emucore/tia/frame-manager/FrameManager.hxx @@ -0,0 +1,116 @@ +//============================================================================ +// +// 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_FRAME_MANAGER +#define TIA_FRAME_MANAGER + +#include "AbstractFrameManager.hxx" +#include "TIAConstants.hxx" +#include "bspf.hxx" +#include "JitterEmulation.hxx" + +class FrameManager: public AbstractFrameManager { + public: + + FrameManager(); + + public: + + void setJitterFactor(uInt8 factor) override { myJitterEmulation.setJitterFactor(factor); } + + bool jitterEnabled() const override { return myJitterEnabled; } + + void enableJitter(bool enabled) override { myJitterEnabled = enabled; } + + uInt32 height() const override { return myHeight; } + + void setFixedHeight(uInt32 height) override; + + uInt32 getY() const override { return myY; } + + uInt32 scanlines() const override { return myCurrentFrameTotalLines; } + + Int32 missingScanlines() const override; + + void setYstart(uInt32 ystart) override; + + uInt32 ystart() const override { return myYStart; } + + void setLayout(FrameLayout mode) override { layout(mode); } + + void onSetVsync() override; + + void onNextLine() override; + + void onReset() override; + + void onLayoutChange() override; + + bool onSave(Serializer& out) const override; + + bool onLoad(Serializer& in) override; + + string name() const override { return "TIA_FrameManager"; } + + private: + + enum State { + waitForVsyncStart, + waitForVsyncEnd, + waitForFrameStart, + frame + }; + + private: + + void updateAutodetectedLayout(); + + void setState(State state); + + void updateIsRendering(); + + private: + + State myState; + uInt32 myLineInState; + uInt32 myVsyncLines; + uInt32 myY, myLastY; + + uInt32 myVblankLines; + uInt32 myKernelLines; + uInt32 myOverscanLines; + uInt32 myFrameLines; + uInt32 myHeight; + uInt32 myFixedHeight; + uInt32 myYStart; + + bool myJitterEnabled; + + Int32 myStableFrameLines; + uInt8 myStableFrameHeightCountdown; + + JitterEmulation myJitterEmulation; + + private: + + FrameManager(const FrameManager&) = delete; + FrameManager(FrameManager&&) = delete; + FrameManager& operator=(const FrameManager&) = delete; + FrameManager& operator=(FrameManager&&) = delete; +}; + +#endif // TIA_FRAME_MANAGER diff --git a/src/emucore/tia/frame-manager/JitterEmulation.cxx b/src/emucore/tia/frame-manager/JitterEmulation.cxx new file mode 100644 index 000000000..db6c48372 --- /dev/null +++ b/src/emucore/tia/frame-manager/JitterEmulation.cxx @@ -0,0 +1,123 @@ +//============================================================================ +// +// 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 "JitterEmulation.hxx" + +enum Metrics: uInt32 { + framesForStableHeight = 2, + minDeltaForJitter = 3, + maxJitter = 50 +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +JitterEmulation::JitterEmulation() : + myYStart(0) +{} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void JitterEmulation::reset() +{ + myLastFrameScanlines = 0; + myStableFrameFinalLines = 0; + myStableFrames = 0; + myStabilizationCounter = 0; + myJitter = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void JitterEmulation::frameComplete(uInt32 scanlineCount) +{ + if (scanlineCount != myStableFrameFinalLines) { + if (scanlineCount == myLastFrameScanlines) { + + if (++myStabilizationCounter >= Metrics::framesForStableHeight) { + if (myStableFrameFinalLines > 0) updateJitter(scanlineCount - myStableFrameFinalLines); + + myStableFrameFinalLines = scanlineCount; + } + + } + else myStabilizationCounter = 0; + } + + myLastFrameScanlines = scanlineCount; + + if (myJitter > 0) myJitter = std::max(myJitter - myJitterFactor, 0); + if (myJitter < 0) myJitter = std::min(myJitter + myJitterFactor, 0); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void JitterEmulation::updateJitter(Int32 scanlineDifference) +{ + if (uInt32(abs(scanlineDifference)) < Metrics::minDeltaForJitter) return; + + Int32 jitter = std::min(jitter, Metrics::maxJitter); + jitter = std::max(jitter, -myYStart); + + if (jitter > 0) jitter += myJitterFactor; + if (jitter < 0) jitter -= myJitterFactor; + + if (abs(jitter) > abs(myJitter)) myJitter = jitter; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool JitterEmulation::save(Serializer& out) const +{ + try { + out.putString(name()); + + out.putInt(myLastFrameScanlines); + out.putInt(myStableFrameFinalLines); + out.putInt(myStableFrames); + out.putInt(myStabilizationCounter); + out.putInt(myJitter); + out.putInt(myJitterFactor); + out.putInt(myYStart); + } + catch(...) + { + cerr << "ERROR: JitterEmulation::save" << std::endl; + + return false; + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool JitterEmulation::load(Serializer& in) +{ + try { + if (in.getString() != name()) return false; + + myLastFrameScanlines = in.getInt(); + myStableFrameFinalLines = in.getInt(); + myStableFrames = in.getInt(); + myStabilizationCounter = in.getInt(); + myJitter = in.getInt(); + myJitterFactor = in.getInt(); + myYStart = in.getInt(); + } + catch (...) + { + cerr << "ERROR: JitterEmulation::load" << std::endl; + + return false; + } + + return true; +} diff --git a/src/emucore/tia/frame-manager/JitterEmulation.hxx b/src/emucore/tia/frame-manager/JitterEmulation.hxx new file mode 100644 index 000000000..6e67eb62c --- /dev/null +++ b/src/emucore/tia/frame-manager/JitterEmulation.hxx @@ -0,0 +1,81 @@ +//============================================================================ +// +// 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_JITTER_EMULATION +#define TIA_JITTER_EMULATION + +#include "bspf.hxx" +#include "Serializable.hxx" + +class JitterEmulation: public Serializable { + public: + + JitterEmulation(); + + public: + + void reset(); + + void frameComplete(uInt32 scanlineCount); + + void setJitterFactor(Int32 factor) { myJitterFactor = factor; } + + Int32 jitter() const { return myJitter; } + + void setYStart(uInt32 ystart) { myYStart = ystart; } + + /** + * Save state. + */ + bool save(Serializer& out) const override; + + /** + * Restore state. + */ + bool load(Serializer& in) override; + + string name() const override { return "JitterEmulation"; } + + private: + + void updateJitter(Int32 scanlineDifference); + + private: + + uInt32 myLastFrameScanlines; + + uInt32 myStableFrameFinalLines; + + uInt32 myStableFrames; + + uInt32 myStabilizationCounter; + + Int32 myJitter; + + Int32 myJitterFactor; + + uInt32 myYStart; + + private: + + JitterEmulation(const JitterEmulation&) = delete; + JitterEmulation(JitterEmulation&&) = delete; + JitterEmulation& operator=(const JitterEmulation&) = delete; + JitterEmulation& operator=(JitterEmulation&&) = delete; +}; + +#endif // TIA_JITTER_EMULATION diff --git a/src/emucore/tia/frame-manager/module.mk b/src/emucore/tia/frame-manager/module.mk new file mode 100644 index 000000000..9bd6093c2 --- /dev/null +++ b/src/emucore/tia/frame-manager/module.mk @@ -0,0 +1,14 @@ +MODULE := src/emucore/tia/frame-manager + +MODULE_OBJS := \ + src/emucore/tia/frame-manager/FrameManager.o \ + src/emucore/tia/frame-manager/AbstractFrameManager.o \ + src/emucore/tia/frame-manager/FrameLayoutDetector.o \ + src/emucore/tia/frame-manager/YStartDetector.o \ + src/emucore/tia/frame-manager/JitterEmulation.o + +MODULE_DIRS += \ + src/emucore/tia/frame-manager + +# Include common rules +include $(srcdir)/common.rules diff --git a/src/emucore/tia/module.mk b/src/emucore/tia/module.mk index a1384df10..ef2adef45 100644 --- a/src/emucore/tia/module.mk +++ b/src/emucore/tia/module.mk @@ -2,7 +2,6 @@ MODULE := src/emucore/tia MODULE_OBJS := \ src/emucore/tia/TIA.o \ - src/emucore/tia/FrameManager.o \ src/emucore/tia/Playfield.o \ src/emucore/tia/DrawCounterDecodes.o \ src/emucore/tia/Missile.o \ @@ -10,8 +9,7 @@ MODULE_OBJS := \ src/emucore/tia/Ball.o \ src/emucore/tia/Background.o \ src/emucore/tia/LatchedInput.o \ - src/emucore/tia/PaddleReader.o \ - src/emucore/tia/VblankManager.o + src/emucore/tia/PaddleReader.o MODULE_DIRS += \ src/emucore/tia diff --git a/src/gui/GameInfoDialog.cxx b/src/gui/GameInfoDialog.cxx index 816ee55a0..f102aa10a 100644 --- a/src/gui/GameInfoDialog.cxx +++ b/src/gui/GameInfoDialog.cxx @@ -27,7 +27,7 @@ #include "Props.hxx" #include "PropsSet.hxx" #include "TabWidget.hxx" -#include "FrameManager.hxx" +#include "TIAConstants.hxx" #include "Widget.hxx" #include "GameInfoDialog.hxx" @@ -310,8 +310,8 @@ GameInfoDialog::GameInfoDialog( t = new StaticTextWidget(myTab, font, hSpace, ypos+1, "YStart ", kTextAlignLeft); myYStart = new SliderWidget(myTab, font, t->getRight(), ypos, 8*fontWidth, lineHeight, "", 0, kYStartChanged); - myYStart->setMinValue(FrameManager::minYStart-1); - myYStart->setMaxValue(FrameManager::maxYStart); + myYStart->setMinValue(TIAConstants::minYStart-1); + myYStart->setMaxValue(TIAConstants::maxYStart); wid.push_back(myYStart); myYStartLabel = new StaticTextWidget(myTab, font, myYStart->getRight() + 4, ypos+1, 5*fontWidth, fontHeight, "", kTextAlignLeft); @@ -321,8 +321,8 @@ GameInfoDialog::GameInfoDialog( t = new StaticTextWidget(myTab, font, hSpace, ypos+1, "Height ", kTextAlignLeft); myHeight = new SliderWidget(myTab, font, t->getRight(), ypos, 8*fontWidth, lineHeight, "", 0, kHeightChanged); - myHeight->setMinValue(FrameManager::minViewableHeight-1); - myHeight->setMaxValue(FrameManager::maxViewableHeight); + myHeight->setMinValue(TIAConstants::minViewableHeight-1); + myHeight->setMaxValue(TIAConstants::maxViewableHeight); wid.push_back(myHeight); myHeightLabel = new StaticTextWidget(myTab, font, myHeight->getRight() + 4, ypos+1, 5*fontWidth, fontHeight, "", kTextAlignLeft); @@ -647,14 +647,14 @@ void GameInfoDialog::handleCommand(CommandSender* sender, int cmd, } case kYStartChanged: - if(myYStart->getValue() == FrameManager::minYStart-1) + if(myYStart->getValue() == TIAConstants::minYStart-1) myYStartLabel->setLabel("Auto"); else myYStartLabel->setValue(myYStart->getValue()); break; case kHeightChanged: - if(myHeight->getValue() == FrameManager::minViewableHeight-1) + if(myHeight->getValue() == TIAConstants::minViewableHeight-1) myHeightLabel->setLabel("Auto"); else myHeightLabel->setValue(myHeight->getValue()); diff --git a/src/gui/RomInfoWidget.cxx b/src/gui/RomInfoWidget.cxx index d5196cc4d..33de534dc 100644 --- a/src/gui/RomInfoWidget.cxx +++ b/src/gui/RomInfoWidget.cxx @@ -19,6 +19,7 @@ #include "OSystem.hxx" #include "Settings.hxx" #include "Widget.hxx" +#include "TIAConstants.hxx" #include "RomInfoWidget.hxx" @@ -28,8 +29,8 @@ RomInfoWidget::RomInfoWidget(GuiObject* boss, const GUI::Font& font, : Widget(boss, font, x, y, w, h), mySurfaceIsValid(false), myHaveProperties(false), - myAvail(w > 400 ? GUI::Size(640, FrameManager::maxViewableHeight*2) : - GUI::Size(320, FrameManager::maxViewableHeight)) + myAvail(w > 400 ? GUI::Size(640, TIAConstants::maxViewableHeight*2) : + GUI::Size(320, TIAConstants::maxViewableHeight)) { _flags = WIDGET_ENABLED; _bgcolor = _bgcolorhi = kWidColor; @@ -76,7 +77,7 @@ void RomInfoWidget::parseProperties() // only draw certain parts of it if(mySurface == nullptr) { - mySurface = instance().frameBuffer().allocateSurface(320*2, FrameManager::maxViewableHeight*2); + mySurface = instance().frameBuffer().allocateSurface(320*2, TIAConstants::maxViewableHeight*2); mySurface->attributes().smoothing = true; mySurface->applyAttributes(); diff --git a/src/macosx/stella.xcodeproj/project.pbxproj b/src/macosx/stella.xcodeproj/project.pbxproj index 30a3ff81d..655cc4dd3 100644 --- a/src/macosx/stella.xcodeproj/project.pbxproj +++ b/src/macosx/stella.xcodeproj/project.pbxproj @@ -366,8 +366,6 @@ DC6C726313CDEA0A008A5975 /* LoggerDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC6C726113CDEA0A008A5975 /* LoggerDialog.hxx */; }; DC6D39871A3CE65000171E71 /* CartWDWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC6D39851A3CE65000171E71 /* CartWDWidget.cxx */; }; DC6D39881A3CE65000171E71 /* CartWDWidget.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC6D39861A3CE65000171E71 /* CartWDWidget.hxx */; }; - DC72B2221E356F4F009056D0 /* VblankManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC72B2201E356F4F009056D0 /* VblankManager.cxx */; }; - DC72B2231E356F4F009056D0 /* VblankManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC72B2211E356F4F009056D0 /* VblankManager.hxx */; }; DC73BD851915E5B1003FAFAD /* FBSurfaceSDL2.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC73BD831915E5B1003FAFAD /* FBSurfaceSDL2.cxx */; }; DC73BD861915E5B1003FAFAD /* FBSurfaceSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC73BD841915E5B1003FAFAD /* FBSurfaceSDL2.hxx */; }; DC73BD891915E5E3003FAFAD /* FBSurface.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC73BD871915E5E3003FAFAD /* FBSurface.cxx */; }; @@ -563,8 +561,6 @@ DCF3A6EE1DFC75E3008A8AF3 /* DelayQueueMember.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6D41DFC75E3008A8AF3 /* DelayQueueMember.hxx */; }; DCF3A6EF1DFC75E3008A8AF3 /* DrawCounterDecodes.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6D51DFC75E3008A8AF3 /* DrawCounterDecodes.cxx */; }; DCF3A6F01DFC75E3008A8AF3 /* DrawCounterDecodes.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6D61DFC75E3008A8AF3 /* DrawCounterDecodes.hxx */; }; - DCF3A6F11DFC75E3008A8AF3 /* FrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6D71DFC75E3008A8AF3 /* FrameManager.cxx */; }; - DCF3A6F21DFC75E3008A8AF3 /* FrameManager.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6D81DFC75E3008A8AF3 /* FrameManager.hxx */; }; DCF3A6F31DFC75E3008A8AF3 /* LatchedInput.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6D91DFC75E3008A8AF3 /* LatchedInput.cxx */; }; DCF3A6F41DFC75E3008A8AF3 /* LatchedInput.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCF3A6DA1DFC75E3008A8AF3 /* LatchedInput.hxx */; }; DCF3A6F51DFC75E3008A8AF3 /* Missile.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCF3A6DB1DFC75E3008A8AF3 /* Missile.cxx */; }; @@ -594,6 +590,16 @@ DCFF14CE18B0260300A20364 /* EventHandlerSDL2.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */; }; DCFFE59D12100E1400DFA000 /* ComboDialog.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */; }; DCFFE59E12100E1400DFA000 /* ComboDialog.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DCFFE59C12100E1400DFA000 /* ComboDialog.hxx */; }; + E0306E0C1F93E916003DDD52 /* YStartDetector.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E061F93E915003DDD52 /* YStartDetector.cxx */; }; + E0306E0D1F93E916003DDD52 /* FrameLayoutDetector.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E071F93E915003DDD52 /* FrameLayoutDetector.hxx */; }; + E0306E0E1F93E916003DDD52 /* YStartDetector.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E081F93E915003DDD52 /* YStartDetector.hxx */; }; + E0306E0F1F93E916003DDD52 /* JitterEmulation.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E091F93E915003DDD52 /* JitterEmulation.cxx */; }; + E0306E101F93E916003DDD52 /* FrameLayoutDetector.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */; }; + E0306E111F93E916003DDD52 /* JitterEmulation.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */; }; + E0406FB61F81A85400A82AE0 /* AbstractFrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */; }; + E0406FB71F81A85400A82AE0 /* AbstractFrameManager.hxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD791F81A358000F3505 /* AbstractFrameManager.hxx */; }; + E0406FB81F81A85400A82AE0 /* FrameManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */; }; + E0406FB91F81A85400A82AE0 /* FrameManager.hxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DFDD7C1F81A358000F3505 /* FrameManager.hxx */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -604,6 +610,7 @@ isEditable = 1; outputFiles = ( ); + script = ""; }; DC5EE7DF14F7C32D001C628C /* PBXBuildRule */ = { isa = PBXBuildRule; @@ -612,6 +619,7 @@ isEditable = 1; outputFiles = ( ); + script = ""; }; DC5EE7E014F7C32D001C628C /* PBXBuildRule */ = { isa = PBXBuildRule; @@ -620,6 +628,7 @@ isEditable = 1; outputFiles = ( ); + script = ""; }; /* End PBXBuildRule section */ @@ -1004,8 +1013,6 @@ DC6C726113CDEA0A008A5975 /* LoggerDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LoggerDialog.hxx; sourceTree = ""; }; DC6D39851A3CE65000171E71 /* CartWDWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartWDWidget.cxx; sourceTree = ""; }; DC6D39861A3CE65000171E71 /* CartWDWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartWDWidget.hxx; sourceTree = ""; }; - DC72B2201E356F4F009056D0 /* VblankManager.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VblankManager.cxx; sourceTree = ""; }; - DC72B2211E356F4F009056D0 /* VblankManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VblankManager.hxx; sourceTree = ""; }; DC73BD831915E5B1003FAFAD /* FBSurfaceSDL2.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FBSurfaceSDL2.cxx; sourceTree = ""; }; DC73BD841915E5B1003FAFAD /* FBSurfaceSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FBSurfaceSDL2.hxx; sourceTree = ""; }; DC73BD871915E5E3003FAFAD /* FBSurface.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FBSurface.cxx; sourceTree = ""; }; @@ -1203,8 +1210,6 @@ DCF3A6D41DFC75E3008A8AF3 /* DelayQueueMember.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueMember.hxx; sourceTree = ""; }; DCF3A6D51DFC75E3008A8AF3 /* DrawCounterDecodes.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DrawCounterDecodes.cxx; sourceTree = ""; }; DCF3A6D61DFC75E3008A8AF3 /* DrawCounterDecodes.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DrawCounterDecodes.hxx; sourceTree = ""; }; - DCF3A6D71DFC75E3008A8AF3 /* FrameManager.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = ""; }; - DCF3A6D81DFC75E3008A8AF3 /* FrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameManager.hxx; sourceTree = ""; }; DCF3A6D91DFC75E3008A8AF3 /* LatchedInput.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LatchedInput.cxx; sourceTree = ""; }; DCF3A6DA1DFC75E3008A8AF3 /* LatchedInput.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LatchedInput.hxx; sourceTree = ""; }; DCF3A6DB1DFC75E3008A8AF3 /* Missile.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Missile.cxx; sourceTree = ""; }; @@ -1234,6 +1239,16 @@ DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EventHandlerSDL2.hxx; sourceTree = ""; }; DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ComboDialog.cxx; sourceTree = ""; }; DCFFE59C12100E1400DFA000 /* ComboDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ComboDialog.hxx; sourceTree = ""; }; + E0306E061F93E915003DDD52 /* YStartDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = YStartDetector.cxx; sourceTree = ""; }; + E0306E071F93E915003DDD52 /* FrameLayoutDetector.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameLayoutDetector.hxx; sourceTree = ""; }; + E0306E081F93E915003DDD52 /* YStartDetector.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = YStartDetector.hxx; sourceTree = ""; }; + E0306E091F93E915003DDD52 /* JitterEmulation.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JitterEmulation.cxx; sourceTree = ""; }; + E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameLayoutDetector.cxx; sourceTree = ""; }; + E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = JitterEmulation.hxx; sourceTree = ""; }; + E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AbstractFrameManager.cxx; sourceTree = ""; }; + E0DFDD791F81A358000F3505 /* AbstractFrameManager.hxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AbstractFrameManager.hxx; sourceTree = ""; }; + E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = ""; }; + E0DFDD7C1F81A358000F3505 /* FrameManager.hxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FrameManager.hxx; sourceTree = ""; }; F5A47A9D01A0482F01D3D55B /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = SOURCE_ROOT; }; F5A47A9E01A0483001D3D55B /* SDLMain.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = SDLMain.m; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ @@ -1975,6 +1990,7 @@ DCE903E31DF5DCD10080A7F3 /* tia */ = { isa = PBXGroup; children = ( + E0DFDD731F81A358000F3505 /* frame-manager */, DCF3A6CD1DFC75E3008A8AF3 /* Background.cxx */, DCF3A6CE1DFC75E3008A8AF3 /* Background.hxx */, DCF3A6CF1DFC75E3008A8AF3 /* Ball.cxx */, @@ -1986,8 +2002,6 @@ DCF3A6D51DFC75E3008A8AF3 /* DrawCounterDecodes.cxx */, DCF3A6D61DFC75E3008A8AF3 /* DrawCounterDecodes.hxx */, DCE8B1861E7E03B300189864 /* FrameLayout.hxx */, - DCF3A6D71DFC75E3008A8AF3 /* FrameManager.cxx */, - DCF3A6D81DFC75E3008A8AF3 /* FrameManager.hxx */, DCF3A6D91DFC75E3008A8AF3 /* LatchedInput.cxx */, DCF3A6DA1DFC75E3008A8AF3 /* LatchedInput.hxx */, DCF3A6DB1DFC75E3008A8AF3 /* Missile.cxx */, @@ -2000,12 +2014,27 @@ DCF3A6E31DFC75E3008A8AF3 /* Playfield.hxx */, DCF3A6E41DFC75E3008A8AF3 /* TIA.cxx */, DCF3A6E51DFC75E3008A8AF3 /* TIA.hxx */, - DC72B2201E356F4F009056D0 /* VblankManager.cxx */, - DC72B2211E356F4F009056D0 /* VblankManager.hxx */, ); path = tia; sourceTree = ""; }; + E0DFDD731F81A358000F3505 /* frame-manager */ = { + isa = PBXGroup; + children = ( + E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */, + E0306E071F93E915003DDD52 /* FrameLayoutDetector.hxx */, + E0306E091F93E915003DDD52 /* JitterEmulation.cxx */, + E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */, + E0306E061F93E915003DDD52 /* YStartDetector.cxx */, + E0306E081F93E915003DDD52 /* YStartDetector.hxx */, + E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */, + E0DFDD791F81A358000F3505 /* AbstractFrameManager.hxx */, + E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */, + E0DFDD7C1F81A358000F3505 /* FrameManager.hxx */, + ); + path = "frame-manager"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2104,7 +2133,6 @@ 2D91742309BA90380026E9FF /* DebuggerParser.hxx in Headers */, 2D91742409BA90380026E9FF /* EditableWidget.hxx in Headers */, DC3EE86F1E2C0E6D00905161 /* zutil.h in Headers */, - DCF3A6F21DFC75E3008A8AF3 /* FrameManager.hxx in Headers */, 2D91742509BA90380026E9FF /* EditTextWidget.hxx in Headers */, DCB87E581A104C1E00BF2A3B /* MediaFactory.hxx in Headers */, 2D91742809BA90380026E9FF /* PackedBitArray.hxx in Headers */, @@ -2126,6 +2154,7 @@ 2D91745209BA90380026E9FF /* CommandDialog.hxx in Headers */, 2D91745309BA90380026E9FF /* CommandMenu.hxx in Headers */, DCB2ECAE1F0AECA3009738A6 /* BSType.hxx in Headers */, + E0306E111F93E916003DDD52 /* JitterEmulation.hxx in Headers */, 2D91745509BA90380026E9FF /* CpuWidget.hxx in Headers */, 2D91745609BA90380026E9FF /* DataGridOpsWidget.hxx in Headers */, 2D91745709BA90380026E9FF /* DataGridWidget.hxx in Headers */, @@ -2161,6 +2190,7 @@ DC47455D09C34BFA00EDDA3A /* CheetahCheat.hxx in Headers */, DC47455F09C34BFA00EDDA3A /* RamCheat.hxx in Headers */, DCD56D390B247D920092F9F8 /* Cart4A50.hxx in Headers */, + E0306E0D1F93E916003DDD52 /* FrameLayoutDetector.hxx in Headers */, DC8078DB0B4BD5F3005E9305 /* DebuggerExpressions.hxx in Headers */, DC8078EB0B4BD697005E9305 /* UIDialog.hxx in Headers */, DCEECE570B5E5E540021D754 /* Cart0840.hxx in Headers */, @@ -2199,7 +2229,6 @@ DCF7B0DE10A762FC007A2870 /* CartF0.hxx in Headers */, DCF7B0E010A762FC007A2870 /* CartFA.hxx in Headers */, DCC527D110B9DA19005E1287 /* Device.hxx in Headers */, - DC72B2231E356F4F009056D0 /* VblankManager.hxx in Headers */, DCC527D310B9DA19005E1287 /* M6502.hxx in Headers */, DC3EE8661E2C0E6D00905161 /* inflate.h in Headers */, DCC527D510B9DA19005E1287 /* NullDev.hxx in Headers */, @@ -2309,6 +2338,7 @@ DCAACB13188D636F00A4D282 /* CartBFWidget.hxx in Headers */, DCAACB15188D636F00A4D282 /* CartDFSCWidget.hxx in Headers */, DC44019F1F1A5D01008C08F6 /* ColorWidget.hxx in Headers */, + E0306E0E1F93E916003DDD52 /* YStartDetector.hxx in Headers */, DC96162D1F817830008A2206 /* AmigaMouseWidget.hxx in Headers */, DCAACB17188D636F00A4D282 /* CartDFWidget.hxx in Headers */, DCF3A6FF1DFC75E3008A8AF3 /* TIA.hxx in Headers */, @@ -2416,6 +2446,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E0406FB61F81A85400A82AE0 /* AbstractFrameManager.cxx in Sources */, + E0406FB71F81A85400A82AE0 /* AbstractFrameManager.hxx in Sources */, + E0406FB81F81A85400A82AE0 /* FrameManager.cxx in Sources */, + E0406FB91F81A85400A82AE0 /* FrameManager.hxx in Sources */, 2D91747409BA90380026E9FF /* SDLMain.m in Sources */, 2D91747509BA90380026E9FF /* Booster.cxx in Sources */, DC3EE8671E2C0E6D00905161 /* inftrees.c in Sources */, @@ -2441,6 +2475,7 @@ 2D91748909BA90380026E9FF /* Console.cxx in Sources */, 2D91748A09BA90380026E9FF /* Control.cxx in Sources */, 2D91748C09BA90380026E9FF /* Driving.cxx in Sources */, + E0306E101F93E916003DDD52 /* FrameLayoutDetector.cxx in Sources */, 2D91748E09BA90380026E9FF /* Joystick.cxx in Sources */, 2D91748F09BA90380026E9FF /* Keyboard.cxx in Sources */, 2D91749009BA90380026E9FF /* M6532.cxx in Sources */, @@ -2584,7 +2619,6 @@ DCAD60A81152F8BD00BC4184 /* CartDPCPlus.cxx in Sources */, DCD6FC7011C281ED005DA767 /* png.c in Sources */, DCD6FC7311C281ED005DA767 /* pngerror.c in Sources */, - DCF3A6F11DFC75E3008A8AF3 /* FrameManager.cxx in Sources */, DCF3A6E71DFC75E3008A8AF3 /* Background.cxx in Sources */, DCD6FC7411C281ED005DA767 /* pngget.c in Sources */, DCD6FC7511C281ED005DA767 /* pngmem.c in Sources */, @@ -2595,6 +2629,7 @@ DCD6FC7A11C281ED005DA767 /* pngrtran.c in Sources */, DC3EE85C1E2C0E6D00905161 /* gzclose.c in Sources */, DCD6FC7B11C281ED005DA767 /* pngrutil.c in Sources */, + E0306E0F1F93E916003DDD52 /* JitterEmulation.cxx in Sources */, DCD6FC7C11C281ED005DA767 /* pngset.c in Sources */, DCD6FC7E11C281ED005DA767 /* pngtrans.c in Sources */, DCD6FC7F11C281ED005DA767 /* pngwio.c in Sources */, @@ -2625,6 +2660,7 @@ DCF3A6F31DFC75E3008A8AF3 /* LatchedInput.cxx in Sources */, DC67270B1556F4860023653B /* CartCTY.cxx in Sources */, DCE395F016CB0B5F008DB1E5 /* FSNodeZIP.cxx in Sources */, + E0306E0C1F93E916003DDD52 /* YStartDetector.cxx in Sources */, DCE395F216CB0B5F008DB1E5 /* ZipHandler.cxx in Sources */, DCAAE5D31715887B0080BB82 /* Cart2KWidget.cxx in Sources */, DCAAE5D51715887B0080BB82 /* Cart3FWidget.cxx in Sources */, @@ -2644,7 +2680,6 @@ DCAAE5E81715887B0080BB82 /* CartF6SCWidget.cxx in Sources */, DCAAE5EA1715887B0080BB82 /* CartF6Widget.cxx in Sources */, DCAAE5EC1715887B0080BB82 /* CartF8SCWidget.cxx in Sources */, - DC72B2221E356F4F009056D0 /* VblankManager.cxx in Sources */, DCAAE5EE1715887B0080BB82 /* CartF8Widget.cxx in Sources */, DCAAE5F01715887B0080BB82 /* CartFAWidget.cxx in Sources */, DCAAE5F21715887B0080BB82 /* CartUAWidget.cxx in Sources */, @@ -2736,6 +2771,7 @@ ../emucore, ../gui, ../yacc, + ../emucore/tia, ., ); INFOPLIST_FILE = "Info-Stella.plist"; @@ -2788,6 +2824,7 @@ ../emucore, ../gui, ../yacc, + ../emucore/tia, ., ); INFOPLIST_FILE = "Info-Stella.plist";