mirror of https://github.com/stella-emu/stella.git
Merge branch 'refactoring/frame_manager'
This commit is contained in:
commit
2c96258890
|
@ -8,3 +8,4 @@ project.xcworkspace/
|
|||
xcuserdata/
|
||||
build/
|
||||
src/macosx/M6502.ins
|
||||
*.dSYM
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
1
Makefile
1
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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<Cartridge>& 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<Cartridge>& cart,
|
|||
my6502 = make_unique<M6502>(myOSystem.settings());
|
||||
myRiot = make_unique<M6532>(*this, myOSystem.settings());
|
||||
myTIA = make_unique<TIA>(*this, myOSystem.sound(), myOSystem.settings());
|
||||
myFrameManager = make_unique<FrameManager>();
|
||||
mySwitches = make_unique<Switches>(myEvent, myProperties);
|
||||
|
||||
myTIA->setFrameManager(myFrameManager.get());
|
||||
|
||||
// Construct the system and components
|
||||
mySystem = make_unique<System>(osystem, *my6502, *myRiot, *myTIA, *myCart);
|
||||
|
||||
|
@ -114,36 +121,19 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<TIA> myTIA;
|
||||
|
||||
// The frame manager instance that is used during emulation.
|
||||
unique_ptr<AbstractFrameManager> myFrameManager;
|
||||
|
||||
// Pointer to the Cartridge (the debugger needs it)
|
||||
unique_ptr<Cartridge> 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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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 <algorithm>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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 <functional>
|
||||
|
||||
#include "VblankManager.hxx"
|
||||
#include "Serializable.hxx"
|
||||
#include "FrameLayout.hxx"
|
||||
#include "bspf.hxx"
|
||||
|
||||
class FrameManager : public Serializable
|
||||
{
|
||||
public:
|
||||
|
||||
using callback = std::function<void()>;
|
||||
|
||||
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
|
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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 <algorithm>
|
||||
|
||||
#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<Int32>(jitter, Metrics::maxJitter);
|
||||
|
||||
if (myMode == VblankMode::final) jitter = std::max<Int32>(jitter, -myLastVblankLines);
|
||||
if (myMode == VblankMode::fixed) jitter = std::max<Int32>(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<Int32>(myYstart + std::min<Int32>(myJitter, Metrics::maxJitter), 0);
|
||||
break;
|
||||
|
||||
// Final mode: use detected ystart value
|
||||
case VblankMode::final:
|
||||
transition = Int32(myCurrentLine) >=
|
||||
std::max<Int32>(myLastVblankLines + std::min<Int32>(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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 <functional>
|
||||
|
||||
#include "Serializable.hxx"
|
||||
#include "FrameLayout.hxx"
|
||||
|
||||
class AbstractFrameManager : public Serializable
|
||||
{
|
||||
public:
|
||||
|
||||
using callback = std::function<void()>;
|
||||
|
||||
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
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 <algorithm>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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
|
|
@ -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<Int32>(jitter, Metrics::maxJitter);
|
||||
jitter = std::max<Int32>(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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 = "<group>"; };
|
||||
DC6D39851A3CE65000171E71 /* CartWDWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartWDWidget.cxx; sourceTree = "<group>"; };
|
||||
DC6D39861A3CE65000171E71 /* CartWDWidget.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartWDWidget.hxx; sourceTree = "<group>"; };
|
||||
DC72B2201E356F4F009056D0 /* VblankManager.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VblankManager.cxx; sourceTree = "<group>"; };
|
||||
DC72B2211E356F4F009056D0 /* VblankManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = VblankManager.hxx; sourceTree = "<group>"; };
|
||||
DC73BD831915E5B1003FAFAD /* FBSurfaceSDL2.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FBSurfaceSDL2.cxx; sourceTree = "<group>"; };
|
||||
DC73BD841915E5B1003FAFAD /* FBSurfaceSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FBSurfaceSDL2.hxx; sourceTree = "<group>"; };
|
||||
DC73BD871915E5E3003FAFAD /* FBSurface.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FBSurface.cxx; sourceTree = "<group>"; };
|
||||
|
@ -1203,8 +1210,6 @@
|
|||
DCF3A6D41DFC75E3008A8AF3 /* DelayQueueMember.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DelayQueueMember.hxx; sourceTree = "<group>"; };
|
||||
DCF3A6D51DFC75E3008A8AF3 /* DrawCounterDecodes.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DrawCounterDecodes.cxx; sourceTree = "<group>"; };
|
||||
DCF3A6D61DFC75E3008A8AF3 /* DrawCounterDecodes.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DrawCounterDecodes.hxx; sourceTree = "<group>"; };
|
||||
DCF3A6D71DFC75E3008A8AF3 /* FrameManager.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = "<group>"; };
|
||||
DCF3A6D81DFC75E3008A8AF3 /* FrameManager.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameManager.hxx; sourceTree = "<group>"; };
|
||||
DCF3A6D91DFC75E3008A8AF3 /* LatchedInput.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LatchedInput.cxx; sourceTree = "<group>"; };
|
||||
DCF3A6DA1DFC75E3008A8AF3 /* LatchedInput.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = LatchedInput.hxx; sourceTree = "<group>"; };
|
||||
DCF3A6DB1DFC75E3008A8AF3 /* Missile.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Missile.cxx; sourceTree = "<group>"; };
|
||||
|
@ -1234,6 +1239,16 @@
|
|||
DCFF14CC18B0260300A20364 /* EventHandlerSDL2.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EventHandlerSDL2.hxx; sourceTree = "<group>"; };
|
||||
DCFFE59B12100E1400DFA000 /* ComboDialog.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ComboDialog.cxx; sourceTree = "<group>"; };
|
||||
DCFFE59C12100E1400DFA000 /* ComboDialog.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ComboDialog.hxx; sourceTree = "<group>"; };
|
||||
E0306E061F93E915003DDD52 /* YStartDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = YStartDetector.cxx; sourceTree = "<group>"; };
|
||||
E0306E071F93E915003DDD52 /* FrameLayoutDetector.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FrameLayoutDetector.hxx; sourceTree = "<group>"; };
|
||||
E0306E081F93E915003DDD52 /* YStartDetector.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = YStartDetector.hxx; sourceTree = "<group>"; };
|
||||
E0306E091F93E915003DDD52 /* JitterEmulation.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JitterEmulation.cxx; sourceTree = "<group>"; };
|
||||
E0306E0A1F93E916003DDD52 /* FrameLayoutDetector.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FrameLayoutDetector.cxx; sourceTree = "<group>"; };
|
||||
E0306E0B1F93E916003DDD52 /* JitterEmulation.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = JitterEmulation.hxx; sourceTree = "<group>"; };
|
||||
E0DFDD781F81A358000F3505 /* AbstractFrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AbstractFrameManager.cxx; sourceTree = "<group>"; };
|
||||
E0DFDD791F81A358000F3505 /* AbstractFrameManager.hxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AbstractFrameManager.hxx; sourceTree = "<group>"; };
|
||||
E0DFDD7B1F81A358000F3505 /* FrameManager.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FrameManager.cxx; sourceTree = "<group>"; };
|
||||
E0DFDD7C1F81A358000F3505 /* FrameManager.hxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FrameManager.hxx; sourceTree = "<group>"; };
|
||||
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 = "<group>";
|
||||
};
|
||||
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 = "<group>";
|
||||
};
|
||||
/* 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";
|
||||
|
|
Loading…
Reference in New Issue