diff --git a/Changes.txt b/Changes.txt
index 0b3081d9c..9fa07beaf 100644
--- a/Changes.txt
+++ b/Changes.txt
@@ -22,6 +22,8 @@
* Added option to start random ROM.
+ * Added option for automatic phosphor.
+
* Enhanced Game Properties dialog for multigame ROMs.
* Added 2nd UI theme and hotkey for toggling UI theme.
diff --git a/docs/graphics/options_video_tv.png b/docs/graphics/options_video_tv.png
index 4cdb07c82..a2a4fe98a 100644
Binary files a/docs/graphics/options_video_tv.png and b/docs/graphics/options_video_tv.png differ
diff --git a/docs/index.html b/docs/index.html
index d5f9978d7..b61627da9 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1309,6 +1309,11 @@
Alt + 3 |
Cmd + 3 |
+
+ Increase 'phosphor' enabling mode |
+ Ctrl-Alt + P |
+ Ctrl-Cmd + P |
+
Toggle 'phosphor' mode |
Alt + P |
@@ -2921,11 +2926,11 @@
- -tv.phosphor <always|byrom> |
- Determine how phosphor mode is enabled. If 'always', then the
- ROM properties entry is ignored, and phosphor mode is always turned
- on. Otherwise, the ROM properties determine whether phosphor mode
- is used for each ROM.
+ | -tv.phosphor <always|auto|byrom> |
+ Determine how phosphor mode is enabled. If 'always' or 'auto', then
+ the ROM properties entry is ignored, and phosphor mode is either always
+ turned on or automatic. Otherwise, the ROM properties determine whether
+ phosphor mode is used for each ROM.
|
@@ -3820,8 +3825,8 @@
Item | Brief description | For more information, see Command Line |
TV mode | Disable TV effects, or select TV preset | -tv.filter |
Adjustable sliders | Set specific attribute in 'Custom' TV mode | -tv.sharpness -tv.resolution, etc. |
- Phosphor for all ROMs | Enable phosphor mode for all ROMs | -tv.phosphor |
- Blend (phosphor) | Blend level to use in phosphor mode for all ROMs
+ |
Phosphor | Select mode for enabling phosphor | -tv.phosphor |
+ Blend (phosphor) | Blend level to use in phosphor modes for all ROMs and automatic
(needs to be manually adjusted for your particular hardware) | -tv.phosblend |
(Scanlines) Intensity | Sets scanlines black-level intensity.
Note: No scanlines in 1x mode snapshots | -tv.scanlines |
diff --git a/src/common/PKeyboardHandler.cxx b/src/common/PKeyboardHandler.cxx
index 74e10c0e1..b75d76fa0 100644
--- a/src/common/PKeyboardHandler.cxx
+++ b/src/common/PKeyboardHandler.cxx
@@ -660,6 +660,8 @@ PhysicalKeyboardHandler::DefaultCommonMapping = {
{ Event::PhosphorDecrease, KBDK_4, KBDM_SHIFT | MOD3 },
{ Event::PhosphorIncrease, KBDK_4, MOD3 },
{ Event::TogglePhosphor, KBDK_P, MOD3 },
+ { Event::PhosphorModeDecrease, KBDK_P, KBDM_SHIFT | KBDM_CTRL | MOD3 },
+ { Event::PhosphorModeIncrease, KBDK_P, KBDM_CTRL | MOD3 },
{ Event::ScanlinesDecrease, KBDK_5, KBDM_SHIFT | MOD3 },
{ Event::ScanlinesIncrease, KBDK_5, MOD3 },
{ Event::PreviousScanlineMask, KBDK_6, KBDM_SHIFT | MOD3 },
diff --git a/src/common/PhosphorHandler.cxx b/src/common/PhosphorHandler.cxx
index db31feb38..9b16c33f7 100644
--- a/src/common/PhosphorHandler.cxx
+++ b/src/common/PhosphorHandler.cxx
@@ -24,23 +24,24 @@ bool PhosphorHandler::initialize(bool enable, int blend)
return false;
myUsePhosphor = enable;
- if(blend >= 0 && blend <= 100)
- myPhosphorPercent = blend / 100.F;
-
- // Used to calculate an averaged color for the 'phosphor' effect
- const auto getPhosphor = [&] (const uInt8 c1, uInt8 c2) -> uInt8 {
- // Use maximum of current and decayed previous values
- c2 = static_cast(c2 * myPhosphorPercent);
- if(c1 > c2) return c1; // raise (assumed immediate)
- else return c2; // decay
- };
// Precalculate the average colors for the 'phosphor' effect
- if(myUsePhosphor)
+ if((myUsePhosphor && blend != -1 && blend / 100.F != myPhosphorPercent) || !myLUTInitialized)
{
+ if(blend >= 0 && blend <= 100)
+ myPhosphorPercent = blend / 100.F;
+
+ // Used to calculate an averaged color for the 'phosphor' effect
+ const auto getPhosphor = [&] (const uInt8 c1, uInt8 c2) -> uInt8 {
+ // Use maximum of current and decayed previous values
+ c2 = static_cast(c2 * myPhosphorPercent);
+ if(c1 > c2) return c1; // raise (assumed immediate)
+ else return c2; // decay
+ };
for(int c = 255; c >= 0; --c)
for(int p = 255; p >= 0; --p)
ourPhosphorLUT[c][p] = getPhosphor(static_cast(c), static_cast(p));
+ myLUTInitialized = true;
}
return true;
}
diff --git a/src/common/PhosphorHandler.hxx b/src/common/PhosphorHandler.hxx
index 7492c28d3..fbe3feb17 100644
--- a/src/common/PhosphorHandler.hxx
+++ b/src/common/PhosphorHandler.hxx
@@ -58,6 +58,7 @@ class PhosphorHandler
// Amount to blend when using phosphor effect
float myPhosphorPercent{0.50F};
+ bool myLUTInitialized{false};
// Precalculated averaged phosphor colors
using PhosphorLUT = BSPF::array2D;
diff --git a/src/common/jsonDefinitions.hxx b/src/common/jsonDefinitions.hxx
index 5be679942..e4c98991b 100644
--- a/src/common/jsonDefinitions.hxx
+++ b/src/common/jsonDefinitions.hxx
@@ -360,6 +360,8 @@ NLOHMANN_JSON_SERIALIZE_ENUM(Event::Type, {
{Event::PhosphorDecrease, "PhosphorDecrease"},
{Event::PhosphorIncrease, "PhosphorIncrease"},
{Event::TogglePhosphor, "TogglePhosphor"},
+ {Event::PhosphorModeDecrease, "PhosphorModeDecrease"},
+ {Event::PhosphorModeIncrease, "PhosphorModeIncrease"},
{Event::ToggleDeveloperSet, "ToggleDeveloperSet"},
{Event::ToggleInter, "ToggleInter"},
{Event::JitterSenseDecrease, "JitterSenseDecrease"},
diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx
index a28657aae..ecc3e2d50 100644
--- a/src/emucore/Console.cxx
+++ b/src/emucore/Console.cxx
@@ -123,7 +123,17 @@ Console::Console(OSystem& osystem, unique_ptr& cart,
// Create subsystems for the console
my6502 = make_unique(myOSystem.settings());
myRiot = make_unique(*this, myOSystem.settings());
- myTIA = make_unique(*this, [this]() { return timing(); }, myOSystem.settings());
+
+ const TIA::onPhosphorCallback callback = [&frameBuffer = this->myOSystem.frameBuffer()](bool enable)
+ {
+ frameBuffer.tiaSurface().enablePhosphor(enable);
+#if DEBUG_BUILD
+ ostringstream msg;
+ msg << "Phosphor effect automatically " << (enable ? "enabled" : "disabled");
+ frameBuffer.showTextMessage(msg.str());
+#endif
+ };
+ myTIA = make_unique(*this, [this]() { return timing(); }, myOSystem.settings(), callback);
myFrameManager = make_unique();
mySwitches = make_unique(myEvent, myProperties, myOSystem.settings());
@@ -615,18 +625,65 @@ void Console::changeSpeed(int direction)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Console::togglePhosphor()
{
- if(myOSystem.frameBuffer().tiaSurface().phosphorEnabled())
- {
+ const bool enable = !myOSystem.frameBuffer().tiaSurface().phosphorEnabled();
+ if(!enable)
myProperties.set(PropType::Display_Phosphor, "NO");
- myOSystem.frameBuffer().tiaSurface().enablePhosphor(false);
- myOSystem.frameBuffer().showTextMessage("Phosphor effect disabled");
- }
else
- {
myProperties.set(PropType::Display_Phosphor, "YES");
- myOSystem.frameBuffer().tiaSurface().enablePhosphor(true);
- myOSystem.frameBuffer().showTextMessage("Phosphor effect enabled");
+ myOSystem.frameBuffer().tiaSurface().enablePhosphor(enable);
+
+ // disable auto-phosphor
+ if(myTIA->autoPhosphorEnabled())
+ myTIA->enableAutoPhosphor(false);
+
+ ostringstream msg;
+ msg << "Phosphor effect " << (enable ? "enabled" : "disabled");
+ myOSystem.frameBuffer().showTextMessage(msg.str());
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void Console::cyclePhosphorMode(int direction)
+{
+ static constexpr std::array MESSAGES = {
+ "by ROM", "always on", "auto-enabled"
+ };
+ static constexpr std::array VALUE = {
+ "byrom", "always", "auto"
+ };
+ const string value = myOSystem.settings().getString("tv.phosphor");
+ int mode;
+
+ for(mode = 2; mode > 0; --mode)
+ if(value == VALUE[mode])
+ break;
+
+ if(direction)
+ {
+ mode = BSPF::clampw(mode + direction, 0, 2);
+ if(mode == 0)
+ {
+ myOSystem.frameBuffer().tiaSurface().enablePhosphor(
+ myProperties.get(PropType::Display_Phosphor) == "YES",
+ BSPF::stoi(myProperties.get(PropType::Display_PPBlend)));
+ myTIA->enableAutoPhosphor(false);
+ }
+ else if(mode == 1)
+ {
+ myOSystem.frameBuffer().tiaSurface().enablePhosphor(
+ true, myOSystem.settings().getInt("tv.phosblend"));
+ myTIA->enableAutoPhosphor(false);
+ }
+ else
+ {
+ myOSystem.frameBuffer().tiaSurface().enablePhosphor(
+ false, myOSystem.settings().getInt("tv.phosblend"));
+ myTIA->enableAutoPhosphor(true);
+ }
+ myOSystem.settings().setValue("tv.phosphor", VALUE[mode]);
}
+ ostringstream msg;
+ msg << "Phosphor mode " << MESSAGES[mode];
+ myOSystem.frameBuffer().showTextMessage(msg.str());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -693,7 +750,7 @@ void Console::initializeAudio()
.updateAudioQueueExtraFragments(myAudioSettings.bufferSize())
.updateAudioQueueHeadroom(myAudioSettings.headroom())
.updateSpeedFactor(myOSystem.settings().getBool("turbo")
- ? 20.0F
+ ? 50.0F
: myOSystem.settings().getFloat("speed"));
createAudioQueue();
diff --git a/src/emucore/Console.hxx b/src/emucore/Console.hxx
index 6784805af..7ab8af4d8 100644
--- a/src/emucore/Console.hxx
+++ b/src/emucore/Console.hxx
@@ -252,6 +252,11 @@ class Console : public Serializable, public ConsoleIO
*/
void togglePhosphor();
+ /**
+ Toggles auto-phosphor.
+ */
+ void cyclePhosphorMode(int direction = +1);
+
/**
Change the "Display.PPBlend" variable.
diff --git a/src/emucore/Event.hxx b/src/emucore/Event.hxx
index bd8573443..18d8ef939 100644
--- a/src/emucore/Event.hxx
+++ b/src/emucore/Event.hxx
@@ -115,7 +115,8 @@ class Event
PreviousAttribute, NextAttribute, DecreaseAttribute, IncreaseAttribute,
ScanlinesDecrease, ScanlinesIncrease,
PreviousScanlineMask, NextScanlineMask,
- PhosphorDecrease, PhosphorIncrease, TogglePhosphor, ToggleInter,
+ PhosphorDecrease, PhosphorIncrease, TogglePhosphor,
+ PhosphorModeDecrease, PhosphorModeIncrease, ToggleInter,
ToggleDeveloperSet, JitterRecDecrease, JitterRecIncrease,
JitterSenseDecrease, JitterSenseIncrease, ToggleJitter,
diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx
index 7a32e9478..d1198a2f7 100644
--- a/src/emucore/EventHandler.cxx
+++ b/src/emucore/EventHandler.cxx
@@ -738,6 +738,22 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated)
}
return;
+ case Event::PhosphorModeDecrease:
+ if(pressed && !repeated)
+ {
+ myOSystem.console().cyclePhosphorMode(-1);
+ myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::PHOSPHOR_MODE);
+ }
+ return;
+
+ case Event::PhosphorModeIncrease:
+ if(pressed && !repeated)
+ {
+ myOSystem.console().cyclePhosphorMode(+1);
+ myGlobalKeyHandler->setSetting(GlobalKeyHandler::Setting::PHOSPHOR_MODE);
+ }
+ return;
+
case Event::ScanlinesDecrease:
if(pressed)
{
@@ -3004,6 +3020,8 @@ EventHandler::EmulActionList EventHandler::ourEmulActionList = { {
{ Event::IncreaseAttribute, "Increase selected 'Custom' attribute" },
// Other TV effects
{ Event::TogglePhosphor, "Toggle 'phosphor' effect" },
+ { Event::PhosphorModeDecrease, "Decrease 'phosphor' enabling mode" },
+ { Event::PhosphorModeIncrease, "Increase 'phosphor' enabling mode" },
{ Event::PhosphorDecrease, "Decrease 'phosphor' blend" },
{ Event::PhosphorIncrease, "Increase 'phosphor' blend" },
{ Event::ScanlinesDecrease, "Decrease scanlines" },
@@ -3181,6 +3199,7 @@ const Event::EventSet EventHandler::AudioVideoEvents = {
Event::PreviousVideoMode, Event::NextVideoMode,
Event::PreviousAttribute, Event::NextAttribute, Event::DecreaseAttribute, Event::IncreaseAttribute,
Event::PhosphorDecrease, Event::PhosphorIncrease, Event::TogglePhosphor,
+ Event::PhosphorModeDecrease, Event::PhosphorModeIncrease,
Event::ScanlinesDecrease, Event::ScanlinesIncrease,
Event::PreviousScanlineMask, Event::NextScanlineMask,
Event::ToggleInter,
diff --git a/src/emucore/EventHandler.hxx b/src/emucore/EventHandler.hxx
index c99dafb89..34beeb36b 100644
--- a/src/emucore/EventHandler.hxx
+++ b/src/emucore/EventHandler.hxx
@@ -540,7 +540,7 @@ class EventHandler
#else
REFRESH_SIZE = 0,
#endif
- EMUL_ACTIONLIST_SIZE = 233 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE,
+ EMUL_ACTIONLIST_SIZE = 235 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE,
MENU_ACTIONLIST_SIZE = 20
;
diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx
index 3bbf1f434..95e27f0cd 100644
--- a/src/emucore/FrameBuffer.cxx
+++ b/src/emucore/FrameBuffer.cxx
@@ -328,12 +328,20 @@ FBInitStatus FrameBuffer::createDisplay(string_view title, BufferType type,
// Phosphor mode can be enabled either globally or per-ROM
int p_blend = 0;
bool enable = false;
+ const string phosphor = myOSystem.settings().getString("tv.phosphor");
- if(myOSystem.settings().getString("tv.phosphor") == "always")
+ myOSystem.console().tia().enableAutoPhosphor(phosphor == "auto");
+
+ if(phosphor == "always")
{
p_blend = myOSystem.settings().getInt("tv.phosblend");
enable = true;
}
+ else if(phosphor == "auto")
+ {
+ p_blend = myOSystem.settings().getInt("tv.phosblend");
+ enable = false;
+ }
else
{
p_blend = BSPF::stoi(myOSystem.console().properties().get(PropType::Display_PPBlend));
@@ -719,7 +727,7 @@ void FrameBuffer::drawFrameStats(float framesPerSecond)
<< "fps @ "
<< std::fixed << std::setprecision(0) << 100 *
(myOSystem.settings().getBool("turbo")
- ? 20.0F
+ ? 50.0F
: myOSystem.settings().getFloat("speed"))
<< "% speed";
diff --git a/src/emucore/GlobalKeyHandler.cxx b/src/emucore/GlobalKeyHandler.cxx
index 6c6874192..67355c43f 100644
--- a/src/emucore/GlobalKeyHandler.cxx
+++ b/src/emucore/GlobalKeyHandler.cxx
@@ -390,6 +390,7 @@ GlobalKeyHandler::SettingData GlobalKeyHandler::getSettingData(const Setting set
{Setting::NTSC_BLEEDING, {true, std::bind(&TIASurface::changeNTSCAdjustable, &myOSystem.frameBuffer().tiaSurface(),
static_cast(NTSCFilter::Adjustables::BLEEDING), _1)}},
// Other TV effects adjustables
+ {Setting::PHOSPHOR_MODE, {true, std::bind(&Console::cyclePhosphorMode, &myOSystem.console(), _1)}},
{Setting::PHOSPHOR, {true, std::bind(&Console::changePhosphor, &myOSystem.console(), _1)}},
{Setting::SCANLINES, {true, std::bind(&TIASurface::changeScanlineIntensity, &myOSystem.frameBuffer().tiaSurface(), _1)}},
{Setting::SCANLINE_MASK, {false, std::bind(&TIASurface::cycleScanlineMask, &myOSystem.frameBuffer().tiaSurface(), _1)}},
diff --git a/src/emucore/GlobalKeyHandler.hxx b/src/emucore/GlobalKeyHandler.hxx
index aef8c3b85..61ba7f91c 100644
--- a/src/emucore/GlobalKeyHandler.hxx
+++ b/src/emucore/GlobalKeyHandler.hxx
@@ -69,6 +69,7 @@ class GlobalKeyHandler
NTSC_FRINGING,
NTSC_BLEEDING,
// Other TV effects adjustables
+ PHOSPHOR_MODE,
PHOSPHOR,
SCANLINES,
SCANLINE_MASK,
diff --git a/src/emucore/ProfilingRunner.cxx b/src/emucore/ProfilingRunner.cxx
index 0c7229295..0adc1723f 100644
--- a/src/emucore/ProfilingRunner.cxx
+++ b/src/emucore/ProfilingRunner.cxx
@@ -125,7 +125,10 @@ bool ProfilingRunner::runOne(const ProfilingRun& run)
M6502 cpu(mySettings);
M6532 riot(consoleIO, mySettings);
- TIA tia(consoleIO, []() { return ConsoleTiming::ntsc; }, mySettings);
+
+ const TIA::onPhosphorCallback callback = [] (bool enable) {};
+
+ TIA tia(consoleIO, []() { return ConsoleTiming::ntsc; }, mySettings, callback);
System system(rng, cpu, riot, tia, *cartridge);
consoleIO.myLeftControl = make_unique(Controller::Jack::Left, event, system);
diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx
index 6b10dc991..0aeba5ed9 100644
--- a/src/emucore/Settings.cxx
+++ b/src/emucore/Settings.cxx
@@ -362,7 +362,7 @@ void Settings::validate()
if(s != "bgopry") setValue("tia.dbgcolors", "roygpb");
s = getString("tv.phosphor");
- if(s != "always" && s != "byrom") setValue("tv.phosphor", "byrom");
+ if(s != "always" && s != "byrom" && s != "auto") setValue("tv.phosphor", "byrom");
i = getInt("tv.phosblend");
if(i < 0 || i > 100) setValue("tv.phosblend", "50");
@@ -576,7 +576,8 @@ void Settings::usage()
<< " -tia.correct_aspect <1|0> Enable aspect ratio correct scaling\n\n"
<< " -tv.filter <0-5> Set TV effects off (0) or to specified mode\n"
<< " (1-5)\n"
- << " -tv.phosphor When to use phosphor mode\n"
+ << " -tv.phosphor When to use phosphor mode\n"
+ << " byrom\n"
<< " -tv.phosblend <0-100> Set default blend level in phosphor mode\n"
<< " -tv.scanlines <0-100> Set scanline intensity to percentage\n"
<< " (0 disables completely)\n"
diff --git a/src/emucore/tia/TIA.cxx b/src/emucore/tia/TIA.cxx
index 78dcba927..795e10e0b 100644
--- a/src/emucore/tia/TIA.cxx
+++ b/src/emucore/tia/TIA.cxx
@@ -63,10 +63,11 @@ static constexpr uInt8 resxLateHblankThreshold = TIAConstants::H_CYCLES - 3;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TIA::TIA(ConsoleIO& console, const ConsoleTimingProvider& timingProvider,
- Settings& settings)
+ Settings& settings, const onPhosphorCallback callback)
: myConsole{console},
myTimingProvider{timingProvider},
mySettings{settings},
+ myPhosphorCallback{callback},
myPlayfield{~CollisionMask::playfield & 0x7FFF},
myMissile0{~CollisionMask::missile0 & 0x7FFF},
myMissile1{~CollisionMask::missile1 & 0x7FFF},
@@ -186,6 +187,12 @@ void TIA::initialize()
myFrontBuffer.fill(0);
myFramebuffer.fill(0);
+ memset(&myPosP0, 0, sizeof(ObjectPos));
+ memset(&myPosP1, 0, sizeof(ObjectPos));
+ memset(&myPosM0, 0, sizeof(ObjectPos));
+ memset(&myPosM1, 0, sizeof(ObjectPos));
+ memset(&myPosBL, 0, sizeof(ObjectPos));
+
applyDeveloperSettings();
// Must be done last, after all other items have reset
@@ -193,6 +200,8 @@ void TIA::initialize()
setFixedColorPalette(mySettings.getString("tia.dbgcolors"));
enableFixedColors(
mySettings.getBool(devSettings ? "dev.debugcolors" : "plr.debugcolors"));
+ myAutoPhosphorEnabled = mySettings.getString("tv.phosphor") == "auto";
+ myAutoPhosphorActive = false;
#ifdef DEBUGGER_SUPPORT
createAccessArrays();
@@ -1396,6 +1405,83 @@ void TIA::onFrameComplete()
myFrontBufferScanlines = scanlinesLastFrame();
+ if(myAutoPhosphorEnabled)
+ {
+ static constexpr int MIN_FLICKER_DELTA = 6;
+ static constexpr int MAX_FLICKER_DELTA = TIAConstants::H_PIXEL - MIN_FLICKER_DELTA;
+ static constexpr int MIN_DIFF = 4;
+ static constexpr int PHOSPHOR_FRAMES = 8;
+
+ int diffCount[FLICKER_FRAMES - 1];
+
+ //cerr << missingScanlines << ", " << myFrontBufferScanlines << " | ";
+ //cerr << myFlickerFrame << ": ";
+ for(int frame = 0; frame < FLICKER_FRAMES - 1; ++frame)
+ {
+ int otherFrame = (myFlickerFrame + frame + 1) % FLICKER_FRAMES;
+ //cerr << otherFrame << " ";
+ // TODO:
+ // - differentiate fast movement and flicker
+ // - movement is directional
+ // - flicker goes back and forth
+ // - ignore disabled objects
+ diffCount[frame] = 0;
+ for(uInt32 y = 0; y < myFrontBufferScanlines; ++y)
+ {
+ int delta;
+ delta = std::abs(myPosP0[y][myFlickerFrame] - myPosP0[y][otherFrame]);
+ if(delta >= MIN_FLICKER_DELTA && delta <= MAX_FLICKER_DELTA)
+ ++diffCount[frame];
+ delta = std::abs(myPosP1[y][myFlickerFrame] - myPosP1[y][otherFrame]);
+ if(delta >= MIN_FLICKER_DELTA && delta <= MAX_FLICKER_DELTA)
+ ++diffCount[frame];
+ delta = std::abs(myPosM0[y][myFlickerFrame] - myPosM0[y][otherFrame]);
+ if(delta >= MIN_FLICKER_DELTA && delta <= MAX_FLICKER_DELTA)
+ ++diffCount[frame];
+ delta = std::abs(myPosM1[y][myFlickerFrame] - myPosM1[y][otherFrame]);
+ if(delta >= MIN_FLICKER_DELTA && delta <= MAX_FLICKER_DELTA)
+ ++diffCount[frame];
+ delta = std::abs(myPosBL[y][myFlickerFrame] - myPosBL[y][otherFrame]);
+ if(delta >= MIN_FLICKER_DELTA && delta <= MAX_FLICKER_DELTA)
+ ++diffCount[frame];
+ }
+ }
+ //cerr << ": ";
+ //for(int i = 0; i < FLICKER_FRAMES - 1; ++i)
+ // cerr << diffCount[i] << ", ";
+ if(diffCount[0] > MIN_DIFF &&
+ (diffCount[0] > diffCount[1] * 1.1 ||
+ diffCount[0] > diffCount[2] * 1.2 ||
+ diffCount[0] > diffCount[3] * 1.3))
+ {
+ if(myFlickerCount < PHOSPHOR_FRAMES)
+ {
+ myFlickerCount += 2; // enabled phosphor twice as fast
+ if(myFlickerCount >= PHOSPHOR_FRAMES && !myAutoPhosphorActive)
+ {
+ myAutoPhosphorActive = true;
+ myPhosphorCallback(true);
+ }
+ }
+ }
+ else if(myFlickerCount)
+ {
+ if(--myFlickerCount == 0 && myAutoPhosphorActive)
+ {
+ myAutoPhosphorActive = false;
+ myPhosphorCallback(false);
+ }
+ }
+ //cerr << "|" << myFlickerCount;
+ //if(myAutoPhosphorActive)
+ // cerr << " *** ON ***\n";
+ //else
+ // cerr << " off\n";
+
+ if(--myFlickerFrame < 0)
+ myFlickerFrame = FLICKER_FRAMES - 1;
+ }
+
++myFramesSinceLastRender;
}
@@ -1552,7 +1638,51 @@ FORCE_INLINE void TIA::nextLine()
myBall.nextLine();
myPlayfield.nextLine();
- if (myFrameManager->isRendering() && myFrameManager->getY() == 0) flushLineCache();
+ if(myFrameManager->isRendering())
+ {
+ if(myFrameManager->getY() == 0)
+ flushLineCache();
+ if(myAutoPhosphorEnabled)
+ {
+ // Test ROMs:
+ // - missing phosphor:
+ // x Princess Rescue: Lives display (~same x-position, different y-position)
+ // - QB: flicker sprite for multi color (same position, different shape and color)
+ // - Star Castle Arcade: vector font flicker (same position, different shape)
+ // - Omega Race: no phosphor enabled (flickers every 2nd frame)
+ // - Riddle of the Sphinx: shots (too small to be detected)
+ // - Missile Command: explosions
+ // - Yars' Revenge: shield, neutral zone (PF flicker)
+ //
+ // - unneccassary phosphor:
+ // - Gas Hog: before game starts (odd invisible position changes)
+ // x Turmoil: M1 rockets (gap between RESM1 and HMOVE?)
+ // x Fathom: seaweed (many sprites moving vertically)
+ // x FourPlay: game start (???)
+ // x Freeway: always (too many sprites?)
+ const uInt32 y = myFrameManager->getY();
+ //const int otherFrame = myFlickerFrame ^ 1;
+ //cerr << y << " ";
+ //if(myPlayer0.getGRPOld() != 0)
+ myPosP0[y][myFlickerFrame] = myPlayer0.getPosition();
+ //if(myPlayer1.getGRPOld() != 0)
+ myPosP1[y][myFlickerFrame] = myPlayer1.getPosition();
+ //if(myMissile0.isOn())
+ myPosM0[y][myFlickerFrame] = myMissile0.getPosition();
+ //else
+ // myPosM0[y][myFlickerFrame] = myPosM0[y][otherFrame];
+ //if(myMissile1.isOn())
+ myPosM1[y][myFlickerFrame] = myMissile1.getPosition();
+ //else
+ // myPosM1[y][myFlickerFrame] = myPosM1[y][otherFrame];
+ //if(myBall.isOn())
+ myPosBL[y][myFlickerFrame] = myBall.getPosition();
+ //else
+ // myPosBL[y][myFlickerFrame] = myPosBL[y][otherFrame];
+
+ //cerr << int(myPlayer0.getPosition()) << " ";
+ }
+ }
mySystem->m6502().clearHaltRequest();
}
@@ -1574,6 +1704,15 @@ void TIA::cloneLastLine()
std::copy_n(myBackBuffer.begin() + (y - 1) * TIAConstants::H_PIXEL,
TIAConstants::H_PIXEL, myBackBuffer.begin() + y * TIAConstants::H_PIXEL);
+
+ if(myAutoPhosphorEnabled)
+ {
+ myPosP0[y][myFlickerFrame] = myPosP0[y - 1][myFlickerFrame];
+ myPosP1[y][myFlickerFrame] = myPosP1[y - 1][myFlickerFrame];
+ myPosM0[y][myFlickerFrame] = myPosM0[y - 1][myFlickerFrame];
+ myPosM1[y][myFlickerFrame] = myPosM1[y - 1][myFlickerFrame];
+ myPosBL[y][myFlickerFrame] = myPosBL[y - 1][myFlickerFrame];
+ }
}
}
diff --git a/src/emucore/tia/TIA.hxx b/src/emucore/tia/TIA.hxx
index cb23147c8..811c80c64 100644
--- a/src/emucore/tia/TIA.hxx
+++ b/src/emucore/tia/TIA.hxx
@@ -109,6 +109,8 @@ class TIA : public Device
friend class TIADebug;
friend class RiotDebug;
+ using onPhosphorCallback = std::function;
+
/**
Create a new TIA for the specified console
@@ -116,7 +118,7 @@ class TIA : public Device
@param settings The settings object for this TIA device
*/
TIA(ConsoleIO& console, const ConsoleTimingProvider& timingProvider,
- Settings& settings);
+ Settings& settings, const onPhosphorCallback callback);
~TIA() override = default;
public:
@@ -277,6 +279,8 @@ class TIA : public Device
Enables/disables color-loss for PAL modes only.
@param enabled Whether to enable or disable PAL color-loss mode
+
+ @return True if color-loss got enabled
*/
bool enableColorLoss(bool enabled);
@@ -294,6 +298,32 @@ class TIA : public Device
*/
bool colorLossActive() const { return myColorLossActive; }
+ /**
+ Enables/disables auto-phosphor.
+
+ @param enabled Whether to enable or disable auto-phosphor mode
+ */
+ void enableAutoPhosphor(bool enabled)
+ {
+ myAutoPhosphorEnabled = enabled;
+ if(!enabled)
+ myAutoPhosphorActive = false;
+ }
+
+ /**
+ Answers whether auto-phosphor is enabled.
+
+ @return Auto-phosphor is enabled
+ */
+ bool autoPhosphorEnabled() const { return myAutoPhosphorEnabled; }
+
+ /**
+ Answers whether auto-phosphor is active.
+
+ @return Auto-phosphor is acitve
+ */
+ bool autoPhosphorActive() const { return myAutoPhosphorActive; }
+
/**
Answers the current color clock we've gotten to on this scanline.
@@ -965,7 +995,17 @@ class TIA : public Device
bool myColorLossEnabled{false};
bool myColorLossActive{false};
- std::array myColorCounts;
+ /**
+ * Auto-phosphor detection variables.
+ */
+ bool myAutoPhosphorEnabled{true};
+ bool myAutoPhosphorActive{true};
+ static constexpr int FLICKER_FRAMES = 1 + 4; // compare current frame with previous 4 frames
+ using ObjectPos = BSPF::array2D;
+ ObjectPos myPosP0, myPosP1, myPosM0, myPosM1, myPosBL;
+ int myFlickerFrame{0};
+ int myFlickerCount{0};
+ onPhosphorCallback myPhosphorCallback;
#ifdef DEBUGGER_SUPPORT
/**
diff --git a/src/gui/GameInfoDialog.cxx b/src/gui/GameInfoDialog.cxx
index 7f2637ff2..e1bd070b7 100644
--- a/src/gui/GameInfoDialog.cxx
+++ b/src/gui/GameInfoDialog.cxx
@@ -160,7 +160,7 @@ void GameInfoDialog::addEmulationTab()
// Phosphor
ypos += lineHeight + VGAP;
myPhosphor = new CheckboxWidget(myTab, _font, HBORDER, ypos + 1,
- "Phosphor (enabled for all ROMs)", kPhosphorChanged);
+ "Phosphor (auto-enabled for all ROMs)", kPhosphorChanged);
myPhosphor->setToolTip(Event::TogglePhosphor);
wid.push_back(myPhosphor);
@@ -770,7 +770,6 @@ void GameInfoDialog::loadEmulationProperties(const Properties& props)
}
}
myTypeDetected->setLabel(bsDetected);
- updateMultiCart();
// Start bank
VarList::push_back(items, "Auto", "AUTO");
@@ -806,10 +805,13 @@ void GameInfoDialog::loadEmulationProperties(const Properties& props)
// if phosphor is always enabled, disable game specific phosphor settings
const bool alwaysPhosphor = instance().settings().getString("tv.phosphor") == "always";
+ const bool autoPhosphor = instance().settings().getString("tv.phosphor") == "auto";
const bool usePhosphor = props.get(PropType::Display_Phosphor) == "YES";
myPhosphor->setState(usePhosphor);
if (alwaysPhosphor)
- myPhosphor->setLabel("Phosphor (enabled for all ROMs)");
+ myPhosphor->setLabel("Phosphor (enabled for all ROMs");
+ else if (autoPhosphor)
+ myPhosphor->setLabel("Phosphor (auto-enabled for all ROMs)");
else
myPhosphor->setLabel("Phosphor");
@@ -823,6 +825,8 @@ void GameInfoDialog::loadEmulationProperties(const Properties& props)
myVCenter->setValueUnit(vcenter ? "px" : "");
mySound->setState(props.get(PropType::Cart_Sound) == "STEREO");
+
+ updateMultiCart();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1191,9 +1195,9 @@ void GameInfoDialog::updateMultiCart()
myFormat->setEnabled(!isMulti);
// if phosphor is always enabled, disable game specific phosphor settings
- const bool alwaysPhosphor = isMulti || instance().settings().getString("tv.phosphor") == "always";
- myPhosphor->setEnabled(!alwaysPhosphor);
- myPPBlend->setEnabled(!alwaysPhosphor && myPhosphor->getState());
+ const bool globalPhosphor = isMulti || instance().settings().getString("tv.phosphor") != "byrom";
+ myPhosphor->setEnabled(!globalPhosphor);
+ myPPBlend->setEnabled(!globalPhosphor && myPhosphor->getState());
myVCenter->setEnabled(!isMulti);
// if stereo is always enabled, disable game specific stereo setting
diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx
index ef4926403..50b74ad6a 100644
--- a/src/gui/VideoAudioDialog.cxx
+++ b/src/gui/VideoAudioDialog.cxx
@@ -377,11 +377,17 @@ void VideoAudioDialog::addTVEffectsTab()
CREATE_CUSTOM_SLIDERS(Bleed, "Bleeding ", 0)
ypos += VGAP * 3;
-
xpos = HBORDER;
// TV Phosphor effect
- myTVPhosphor = new CheckboxWidget(myTab, _font, xpos, ypos + 1, "Phosphor for all ROMs", kPhosphorChanged);
+ items.clear();
+ VarList::push_back(items, "by ROM", "byrom");
+ VarList::push_back(items, "always", "always");
+ VarList::push_back(items, "auto", "auto");
+ myTVPhosphor = new PopUpWidget(myTab, _font, xpos, ypos,
+ _font.getStringWidth("by ROM"), lineHeight,
+ items, "Phosphor ", 0, kPhosphorChanged);
+ myTVPhosphor->setToolTip(Event::PhosphorModeDecrease, Event::PhosphorModeIncrease);
wid.push_back(myTVPhosphor);
ypos += lineHeight + VGAP / 2;
@@ -754,7 +760,7 @@ void VideoAudioDialog::loadConfig()
loadTVAdjustables(NTSCFilter::Preset::CUSTOM);
// TV phosphor mode & blend
- myTVPhosphor->setState(settings.getString("tv.phosphor") == "always");
+ myTVPhosphor->setSelected(settings.getString("tv.phosphor"), "byrom");
myTVPhosLevel->setValue(settings.getInt("tv.phosblend"));
handlePhosphorChange();
@@ -889,7 +895,7 @@ void VideoAudioDialog::saveConfig()
NTSCFilter::saveConfig(settings);
// TV phosphor mode & blend
- settings.setValue("tv.phosphor", myTVPhosphor->getState() ? "always" : "byrom");
+ settings.setValue("tv.phosphor", myTVPhosphor->getSelectedTag());
settings.setValue("tv.phosblend", myTVPhosLevel->getValueLabel() == "Off"
? "0" : myTVPhosLevel->getValueLabel());
@@ -1029,7 +1035,7 @@ void VideoAudioDialog::setDefaults()
myTVMode->setSelected("0", "0");
// TV phosphor mode & blend
- myTVPhosphor->setState(false);
+ myTVPhosphor->setSelected("byrom");
myTVPhosLevel->setValue(50);
// TV scanline intensity & mask
@@ -1209,7 +1215,7 @@ void VideoAudioDialog::handleOverscanChange()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void VideoAudioDialog::handlePhosphorChange()
{
- myTVPhosLevel->setEnabled(myTVPhosphor->getState());
+ myTVPhosLevel->setEnabled(myTVPhosphor->getSelectedTag() != "byrom");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/src/gui/VideoAudioDialog.hxx b/src/gui/VideoAudioDialog.hxx
index 9f391f610..1b349e220 100644
--- a/src/gui/VideoAudioDialog.hxx
+++ b/src/gui/VideoAudioDialog.hxx
@@ -96,7 +96,7 @@ class VideoAudioDialog : public Dialog
SliderWidget* myTVBleed{nullptr};
// TV phosphor effect
- CheckboxWidget* myTVPhosphor{nullptr};
+ PopUpWidget* myTVPhosphor{nullptr};
SliderWidget* myTVPhosLevel{nullptr};
// TV scanline intensity and interpolation