added auto-phosphor (resolves #1009)

This commit is contained in:
thrust26 2024-01-24 11:24:21 +01:00
parent d2c36ae159
commit a430ad6927
22 changed files with 350 additions and 52 deletions

View File

@ -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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -1309,6 +1309,11 @@
<td>Alt + 3</td>
<td>Cmd + 3</td>
</tr>
<tr>
<td><i>Increase</i> 'phosphor' enabling mode</td>
<td>Ctrl-Alt + P</td>
<td>Ctrl-Cmd + P</td>
</tr>
<tr>
<td>Toggle 'phosphor' mode</td>
<td>Alt + P</td>
@ -2921,11 +2926,11 @@
</tr>
<tr>
<td><pre>-tv.phosphor &lt;always|byrom&gt;</pre></td>
<td>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.
<td><pre>-tv.phosphor &lt;always|auto|byrom&gt;</pre></td>
<td>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.
</td>
</tr>
@ -3820,8 +3825,8 @@
<tr><th>Item</th><th>Brief description</th><th>For more information,<br>see <a href="#CommandLine">Command Line</a></th></tr>
<tr><td>TV mode</td><td>Disable TV effects, or select TV preset</td><td>-tv.filter</td></tr>
<tr><td>Adjustable sliders</td><td>Set specific attribute in 'Custom' TV mode</td><td>-tv.sharpness<br/>-tv.resolution, etc.</td></tr>
<tr><td>Phosphor for all ROMs</td><td>Enable phosphor mode for all ROMs</td><td>-tv.phosphor</td></tr>
<tr><td>Blend (phosphor)</td><td>Blend level to use in phosphor mode for all ROMs
<tr><td>Phosphor</td><td>Select mode for enabling phosphor</td><td>-tv.phosphor</td></tr>
<tr><td>Blend (phosphor)</td><td>Blend level to use in phosphor modes for all ROMs and automatic
(needs to be manually adjusted for your particular hardware)</td><td>-tv.phosblend</td></tr>
<tr><td>(Scanlines) Intensity</td><td>Sets scanlines black-level intensity.</br>
Note: No scanlines in 1x mode snapshots</td><td>-tv.scanlines</td></tr>

View File

@ -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 },

View File

@ -24,6 +24,10 @@ bool PhosphorHandler::initialize(bool enable, int blend)
return false;
myUsePhosphor = enable;
// Precalculate the average colors for the 'phosphor' effect
if((myUsePhosphor && blend != -1 && blend / 100.F != myPhosphorPercent) || !myLUTInitialized)
{
if(blend >= 0 && blend <= 100)
myPhosphorPercent = blend / 100.F;
@ -34,13 +38,10 @@ bool PhosphorHandler::initialize(bool enable, int blend)
if(c1 > c2) return c1; // raise (assumed immediate)
else return c2; // decay
};
// Precalculate the average colors for the 'phosphor' effect
if(myUsePhosphor)
{
for(int c = 255; c >= 0; --c)
for(int p = 255; p >= 0; --p)
ourPhosphorLUT[c][p] = getPhosphor(static_cast<uInt8>(c), static_cast<uInt8>(p));
myLUTInitialized = true;
}
return true;
}

View File

@ -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<uInt8, kColor, kColor>;

View File

@ -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"},

View File

@ -123,7 +123,17 @@ Console::Console(OSystem& osystem, unique_ptr<Cartridge>& cart,
// Create subsystems for the console
my6502 = make_unique<M6502>(myOSystem.settings());
myRiot = make_unique<M6532>(*this, myOSystem.settings());
myTIA = make_unique<TIA>(*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<TIA>(*this, [this]() { return timing(); }, myOSystem.settings(), callback);
myFrameManager = make_unique<FrameManager>();
mySwitches = make_unique<Switches>(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(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<string_view, 3> MESSAGES = {
"by ROM", "always on", "auto-enabled"
};
static constexpr std::array<string_view, 3> 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
{
myProperties.set(PropType::Display_Phosphor, "YES");
myOSystem.frameBuffer().tiaSurface().enablePhosphor(true);
myOSystem.frameBuffer().showTextMessage("Phosphor effect enabled");
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();

View File

@ -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.

View File

@ -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,

View File

@ -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,

View File

@ -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
;

View File

@ -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";

View File

@ -390,6 +390,7 @@ GlobalKeyHandler::SettingData GlobalKeyHandler::getSettingData(const Setting set
{Setting::NTSC_BLEEDING, {true, std::bind(&TIASurface::changeNTSCAdjustable, &myOSystem.frameBuffer().tiaSurface(),
static_cast<int>(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)}},

View File

@ -69,6 +69,7 @@ class GlobalKeyHandler
NTSC_FRINGING,
NTSC_BLEEDING,
// Other TV effects adjustables
PHOSPHOR_MODE,
PHOSPHOR,
SCANLINES,
SCANLINE_MASK,

View File

@ -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<Joystick>(Controller::Jack::Left, event, system);

View File

@ -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 <always|byrom> When to use phosphor mode\n"
<< " -tv.phosphor <always|auto|> 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"

View File

@ -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];
}
}
}

View File

@ -109,6 +109,8 @@ class TIA : public Device
friend class TIADebug;
friend class RiotDebug;
using onPhosphorCallback = std::function<void(bool)>;
/**
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<uInt32, 16> 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<uInt8, TIAConstants::frameBufferHeight, FLICKER_FRAMES>;
ObjectPos myPosP0, myPosP1, myPosM0, myPosM1, myPosBL;
int myFlickerFrame{0};
int myFlickerCount{0};
onPhosphorCallback myPhosphorCallback;
#ifdef DEBUGGER_SUPPORT
/**

View File

@ -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

View File

@ -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");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -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