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 @@ ItemBrief descriptionFor more information,
see Command Line TV modeDisable TV effects, or select TV preset-tv.filter Adjustable slidersSet specific attribute in 'Custom' TV mode-tv.sharpness
-tv.resolution, etc. - Phosphor for all ROMsEnable phosphor mode for all ROMs-tv.phosphor - Blend (phosphor)Blend level to use in phosphor mode for all ROMs + PhosphorSelect 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) IntensitySets 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