mirror of https://github.com/stella-emu/stella.git
added auto-phosphor (resolves #1009)
This commit is contained in:
parent
d2c36ae159
commit
a430ad6927
|
@ -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 |
|
@ -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 <always|byrom></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 <always|auto|byrom></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>
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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<uInt8>(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<uInt8>(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<uInt8>(c), static_cast<uInt8>(p));
|
||||
myLUTInitialized = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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"},
|
||||
|
|
|
@ -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(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<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
|
||||
{
|
||||
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();
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
;
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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)}},
|
||||
|
|
|
@ -69,6 +69,7 @@ class GlobalKeyHandler
|
|||
NTSC_FRINGING,
|
||||
NTSC_BLEEDING,
|
||||
// Other TV effects adjustables
|
||||
PHOSPHOR_MODE,
|
||||
PHOSPHOR,
|
||||
SCANLINES,
|
||||
SCANLINE_MASK,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue