diff --git a/Changes.txt b/Changes.txt
index 557157f25..616ec22f7 100644
--- a/Changes.txt
+++ b/Changes.txt
@@ -37,6 +37,8 @@
* Added separate positioning of launcher, emulator and debugger
+ * Added optional display to game refresh rate adaption in fullscreen mode
+
* Added option which lets default ROM path follow launcher navigation
* Added debugger 'saveaccess' function, which saves memory access counts to
diff --git a/docs/graphics/options_video.png b/docs/graphics/options_video.png
index deb857ca4..58654148a 100644
Binary files a/docs/graphics/options_video.png and b/docs/graphics/options_video.png differ
diff --git a/docs/index.html b/docs/index.html
index e5d859ba0..81185180d 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1377,6 +1377,13 @@
Alt + Enter |
Cmd + Enter |
+
+ Toggle adapting display refresh rate to game frame rate
+
+ Note: Not available for macOS. |
+ Alt + r |
+ Cmd + r |
+
Decrease overscan in fullscreen mode |
Shift + PageDown |
@@ -2191,7 +2198,7 @@
-audio.dpc_pitch <10000 - 30000> |
- Set the pitch o f Pitfall II music. |
+ Set the pitch of Pitfall II music. |
@@ -2218,6 +2225,13 @@
aspect ratio.
+
+ -tia.fs_refresh <1|0> |
+ While in fullscreen mode, adapt the display's refresh rate to the game's frame rate
+ to minimize judder.
+ Note: Not available for macOS. |
+
+
-tia.fs_overscan <0 - 10> |
Add overscan to TIA image while in fullscreen mode |
@@ -2943,13 +2957,15 @@
Item | Brief description | For more information, see CommandLine |
Renderer | Use specified rendering mode | -video |
- Interpolation | Interpolation of TIA image | -tia.inter |
- Zoom | Zoom level of TIA image | -tia.zoom |
+ Interpolation | Enable interpolation of the TIA image | -tia.inter |
+ Zoom | Zoom level of the TIA image | -tia.zoom |
Fullscreen | Self-explanatory - Note that colors may slightly change.
This depends on the OS and renderer used. | -fullscreen |
- Stretch | In fullscreen mode, completely fill screen with TIA image | -tia.fs_stretch |
+ Stretch | In fullscreen mode, completely fill screen with the TIA image. | -tia.fs_stretch |
+ Adapt display... | In fullscreen mode, adapt the display's refresh rate to the game's frame rate to minimize judder.
+ Note: Not available for macOS. | -tia.fs_refresh |
Overscan | In fullscreen mode, add overscan to the TIA image | -tia.fs_overscan |
- V-Size adjust | Adjust height of TIA image | -tia.vsizeadjust |
+ V-Size adjust | Adjust height of the TIA image | -tia.vsizeadjust |
diff --git a/src/common/FrameBufferSDL2.cxx b/src/common/FrameBufferSDL2.cxx
index 4d013a35d..d930b2fc0 100644
--- a/src/common/FrameBufferSDL2.cxx
+++ b/src/common/FrameBufferSDL2.cxx
@@ -15,6 +15,8 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
+#include
+
#include "SDL_lib.hxx"
#include "bspf.hxx"
#include "Logger.hxx"
@@ -99,19 +101,32 @@ void FrameBufferSDL2::queryHardware(vector& fullscreenRes,
int numModes = SDL_GetNumDisplayModes(i);
ostringstream s;
- s << "Supported video modes for display " << i << ":";
- Logger::debug(s.str());
+ s << "Supported video modes (" << numModes << ") for display " << i << ":";
+
+ string lastRes = "";
+
for (int m = 0; m < numModes; m++)
{
SDL_DisplayMode mode;
+ ostringstream res;
SDL_GetDisplayMode(i, m, &mode);
- s.str("");
- s << " " << m << ": " << mode.w << "x" << mode.h << "@" << mode.refresh_rate << "Hz";
- if (mode.w == display.w && mode.h == display.h && mode.refresh_rate == display.refresh_rate)
- s << " (active)";
- Logger::debug(s.str());
+ res << std::setw(4) << mode.w << "x" << std::setw(4) << mode.h;
+
+ if(lastRes != res.str())
+ {
+ Logger::debug(s.str());
+ s.str("");
+ lastRes = res.str();
+ s << lastRes << ": ";
+ }
+ s << mode.refresh_rate << "Hz";
+ if(mode.w == display.w && mode.h == display.h && mode.refresh_rate == display.refresh_rate)
+ s << "* ";
+ else
+ s << " ";
}
+ Logger::debug(s.str());
}
// Now get the maximum windowed desktop resolution
@@ -218,21 +233,14 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
if(SDL_WasInit(SDL_INIT_VIDEO) == 0)
return false;
- // TODO: On multiple displays, switching from centered mode, does not respect
- // current window's display (which many not be centered anymore)
+ const bool fullScreen = mode.fsIndex != -1;
+ bool forceCreateRenderer = false;
// Get windowed window's last display
Int32 displayIndex = std::min(myNumDisplays, myOSystem.settings().getInt(getDisplayKey()));
// Get windowed window's last position
myWindowedPos = myOSystem.settings().getPoint(getPositionKey());
- // Always recreate renderer (some systems need this)
- if(myRenderer)
- {
- SDL_DestroyRenderer(myRenderer);
- myRenderer = nullptr;
- }
-
int posX, posY;
myCenter = myOSystem.settings().getBool("center");
@@ -261,49 +269,45 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
posX = BSPF::clamp(posX, x0 - Int32(mode.screen.w) + 50, x1 - 50);
posY = BSPF::clamp(posY, y0 + 50, y1 - 50);
}
- uInt32 flags = mode.fsIndex != -1 ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
- flags |= SDL_WINDOW_ALLOW_HIGHDPI;
- // macOS seems to have issues with destroying the window, and wants to
- // keep the same handle
- // Problem is, doing so on other platforms results in flickering when
- // toggling fullscreen windowed mode
- // So we have a special case for macOS
-#ifndef BSPF_MACOS
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+ SDL_DisplayMode adaptedSdlMode;
+ const bool shouldAdapt = fullScreen && myOSystem.settings().getBool("tia.fs_refresh")
+ && gameRefreshRate()
+ // take care of 59.94 Hz
+ && refreshRate() % gameRefreshRate() != 0 && refreshRate() % (gameRefreshRate() - 1) != 0;
+ const bool adaptRefresh = shouldAdapt && adaptRefreshRate(displayIndex, adaptedSdlMode);
+#else
+ const bool adaptRefresh = false;
+#endif
+ const uInt32 flags = SDL_WINDOW_ALLOW_HIGHDPI
+ | (fullScreen ? adaptRefresh ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
+
// Don't re-create the window if its display and size hasn't changed,
// as it's not necessary, and causes flashing in fullscreen mode
if(myWindow)
{
- int d = SDL_GetWindowDisplayIndex(myWindow);
+ const int d = SDL_GetWindowDisplayIndex(myWindow);
int w, h;
SDL_GetWindowSize(myWindow, &w, &h);
- if(d != displayIndex || uInt32(w) != mode.screen.w || uInt32(h) != mode.screen.h)
+ if(d != displayIndex || uInt32(w) != mode.screen.w || uInt32(h) != mode.screen.h
+ || adaptRefresh)
{
SDL_DestroyWindow(myWindow);
myWindow = nullptr;
}
}
+
if(myWindow)
{
// Even though window size stayed the same, the title may have changed
SDL_SetWindowTitle(myWindow, title.c_str());
SDL_SetWindowPosition(myWindow, posX, posY);
}
-#else
- // macOS wants to *never* re-create the window
- // This sometimes results in the window being resized *after* it's displayed,
- // but at least the code works and doesn't crash
- if(myWindow)
- {
- SDL_SetWindowFullscreen(myWindow, flags);
- SDL_SetWindowSize(myWindow, mode.screen.w, mode.screen.h);
- SDL_SetWindowPosition(myWindow, posX, posY);
- SDL_SetWindowTitle(myWindow, title.c_str());
- }
-#endif
else
{
+ forceCreateRenderer = true;
myWindow = SDL_CreateWindow(title.c_str(), posX, posY,
mode.screen.w, mode.screen.h, flags);
if(myWindow == nullptr)
@@ -312,31 +316,133 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
Logger::error(msg);
return false;
}
+
setWindowIcon();
}
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+ if(adaptRefresh)
+ {
+ // Switch to mode for adapted refresh rate
+ if(SDL_SetWindowDisplayMode(myWindow, &adaptedSdlMode) != 0)
+ {
+ Logger::error("ERROR: Display refresh rate change failed");
+ }
+ else
+ {
+ ostringstream msg;
+
+ msg << "Display refresh rate changed to " << adaptedSdlMode.refresh_rate << " Hz";
+ Logger::info(msg.str());
+ }
+ }
+#endif
+
+ return createRenderer(forceCreateRenderer);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool FrameBufferSDL2::adaptRefreshRate(Int32 displayIndex, SDL_DisplayMode& adaptedSdlMode)
+{
+ SDL_DisplayMode sdlMode;
+
+ if(SDL_GetCurrentDisplayMode(displayIndex, &sdlMode) != 0)
+ {
+ Logger::error("ERROR: Display mode could not be retrieved");
+ return false;
+ }
+
+ const int currentRefreshRate = sdlMode.refresh_rate;
+ const int wantedRefreshRate = gameRefreshRate();
+ // Take care of rounded refresh rates (e.g. 59.94 Hz)
+ float factor = std::min(float(currentRefreshRate) / wantedRefreshRate,
+ float(currentRefreshRate) / (wantedRefreshRate - 1));
+ // Calculate difference taking care of integer factors (e.g. 100/120)
+ float bestDiff = std::abs(factor - std::round(factor)) / factor;
+ bool adapt = false;
+
+ // Display refresh rate should be an integer factor of the game's refresh rate
+ // Note: Modes are scanned with size being first priority,
+ // therefore the size will never change.
+ // Check for integer factors 1 (60/50 Hz) and 2 (120/100 Hz)
+ for(int m = 1; m <= 2; ++m)
+ {
+ SDL_DisplayMode closestSdlMode;
+
+ sdlMode.refresh_rate = wantedRefreshRate * m;
+ if(SDL_GetClosestDisplayMode(displayIndex, &sdlMode, &closestSdlMode) == nullptr)
+ {
+ Logger::error("ERROR: Closest display mode could not be retrieved");
+ return adapt;
+ }
+ factor = std::min(float(sdlMode.refresh_rate) / sdlMode.refresh_rate,
+ float(sdlMode.refresh_rate) / (sdlMode.refresh_rate - 1));
+ const float diff = std::abs(factor - std::round(factor)) / factor;
+ if(diff < bestDiff)
+ {
+ bestDiff = diff;
+ adaptedSdlMode = closestSdlMode;
+ adapt = true;
+ }
+ }
+ //cerr << "refresh rate adapt ";
+ //if(adapt)
+ // cerr << "required (" << currentRefreshRate << " Hz -> " << adaptedSdlMode.refresh_rate << " Hz)";
+ //else
+ // cerr << "not required/possible";
+ //cerr << endl;
+
+ // Only change if the display supports a better refresh rate
+ return adapt;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+bool FrameBufferSDL2::createRenderer(bool force)
+{
+ // A new renderer is only created when necessary:
+ // - new myWindow (force = true)
+ // - no renderer existing
+ // - different renderer flags
+ // - different renderer name
+ bool recreate = force || myRenderer == nullptr;
uInt32 renderFlags = SDL_RENDERER_ACCELERATED;
+ const string& video = myOSystem.settings().getString("video"); // Render hint
+ SDL_RendererInfo renderInfo;
+
if(myOSystem.settings().getBool("vsync")
&& !myOSystem.settings().getBool("turbo")) // V'synced blits option
renderFlags |= SDL_RENDERER_PRESENTVSYNC;
- const string& video = myOSystem.settings().getString("video"); // Render hint
- if(video != "")
- SDL_SetHint(SDL_HINT_RENDER_DRIVER, video.c_str());
- myRenderer = SDL_CreateRenderer(myWindow, -1, renderFlags);
+ // check renderer flags and name
+ recreate |= (SDL_GetRendererInfo(myRenderer, &renderInfo) != 0)
+ || ((renderInfo.flags & (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)) != renderFlags
+ || (video != renderInfo.name));
- detectFeatures();
- determineDimensions();
-
- if(myRenderer == nullptr)
+ if(recreate)
{
- string msg = "ERROR: Unable to create SDL renderer: " + string(SDL_GetError());
- Logger::error(msg);
- return false;
+ //cerr << "Create new renderer for buffer type #" << int(myBufferType) << endl;
+ if(myRenderer)
+ SDL_DestroyRenderer(myRenderer);
+
+ if(video != "")
+ SDL_SetHint(SDL_HINT_RENDER_DRIVER, video.c_str());
+
+ myRenderer = SDL_CreateRenderer(myWindow, -1, renderFlags);
+
+ detectFeatures();
+ determineDimensions();
+
+ if(myRenderer == nullptr)
+ {
+ string msg = "ERROR: Unable to create SDL renderer: " + string(SDL_GetError());
+ Logger::error(msg);
+ return false;
+ }
}
clear();
SDL_RendererInfo renderinfo;
+
if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0)
myOSystem.settings().setValue("video", renderinfo.name);
@@ -404,6 +510,36 @@ bool FrameBufferSDL2::fullScreen() const
#endif
}
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int FrameBufferSDL2::refreshRate() const
+{
+ ASSERT_MAIN_THREAD;
+
+ const uInt32 displayIndex = SDL_GetWindowDisplayIndex(myWindow);
+ SDL_DisplayMode sdlMode;
+
+ if(SDL_GetCurrentDisplayMode(displayIndex, &sdlMode) == 0)
+ return sdlMode.refresh_rate;
+
+ if(myWindow != nullptr)
+ Logger::error("Could not retrieve current display mode");
+
+ return 0;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+int FrameBufferSDL2::gameRefreshRate() const
+{
+ if(myOSystem.hasConsole())
+ {
+ const string format = myOSystem.console().getFormatString();
+ const bool isNtsc = format == "NTSC" || format == "PAL60" || format == "SECAM60";
+
+ return isNtsc ? 60 : 50; // The code will take care of 59/49 Hz
+ }
+ return 0;
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBufferSDL2::renderToScreen()
{
@@ -416,10 +552,9 @@ void FrameBufferSDL2::renderToScreen()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBufferSDL2::setWindowIcon()
{
- ASSERT_MAIN_THREAD;
-
#if !defined(BSPF_MACOS) && !defined(RETRON77)
#include "stella_icon.hxx"
+ ASSERT_MAIN_THREAD;
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(stella_icon, 32, 32, 32,
32 * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000);
diff --git a/src/common/FrameBufferSDL2.hxx b/src/common/FrameBufferSDL2.hxx
index d2825037c..7904ed0ee 100644
--- a/src/common/FrameBufferSDL2.hxx
+++ b/src/common/FrameBufferSDL2.hxx
@@ -181,6 +181,25 @@ class FrameBufferSDL2 : public FrameBuffer
*/
bool setVideoMode(const string& title, const VideoMode& mode) override;
+ /**
+ Checks if the display refresh rate should be adapted to game refresh rate in (real) fullscreen mode
+
+ @param displayIndex The display which should be checked
+ @param adaptedSdlMode The best matching mode if the refresh rate should be changed
+
+ @return True if the refresh rate should be changed
+ */
+ bool adaptRefreshRate(Int32 displayIndex, SDL_DisplayMode& adaptedSdlMode);
+
+ /**
+ Create a new renderer if required
+
+ @param force If true, force new renderer creation
+
+ @return False on any errors, else true
+ */
+ bool createRenderer(bool force);
+
/**
This method is called to create a surface with the given attributes.
@@ -233,6 +252,16 @@ class FrameBufferSDL2 : public FrameBuffer
*/
void determineDimensions();
+ /**
+ Retrieve the current display's refresh rate, or 0 if no window
+ */
+ int refreshRate() const override;
+
+ /**
+ Retrieve the current game's refresh rate, or 60 if no game
+ */
+ int gameRefreshRate() const;
+
private:
// The SDL video buffer
SDL_Window* myWindow{nullptr};
diff --git a/src/common/PKeyboardHandler.cxx b/src/common/PKeyboardHandler.cxx
index cf4a31831..81b25cd6e 100644
--- a/src/common/PKeyboardHandler.cxx
+++ b/src/common/PKeyboardHandler.cxx
@@ -467,6 +467,7 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::SoundToggle, KBDK_RIGHTBRACKET, KBDM_CTRL},
{Event::ToggleFullScreen, KBDK_RETURN, MOD3},
+ {Event::ToggleAdaptRefresh, KBDK_R, MOD3},
{Event::OverscanDecrease, KBDK_PAGEDOWN, KBDM_SHIFT},
{Event::OverscanIncrease, KBDK_PAGEUP, KBDM_SHIFT},
//{Event::VidmodeStd, KBDK_1, MOD3},
diff --git a/src/common/bspf.hxx b/src/common/bspf.hxx
index f21a99a4c..3fc1aaf6c 100644
--- a/src/common/bspf.hxx
+++ b/src/common/bspf.hxx
@@ -101,6 +101,12 @@ static const string EmptyString("");
#undef PAGE_SIZE
#undef PAGE_MASK
+// Adaptable refresh is currently not available on MacOS
+// In the future, this may expand to other systems
+#if !defined(BSPF_MACOS)
+ #define ADAPTABLE_REFRESH_SUPPORT
+#endif
+
namespace BSPF
{
static constexpr float PI_f = 3.141592653589793238462643383279502884F;
diff --git a/src/emucore/Event.hxx b/src/emucore/Event.hxx
index fa8a4d04d..ddac653f3 100644
--- a/src/emucore/Event.hxx
+++ b/src/emucore/Event.hxx
@@ -123,6 +123,7 @@ class Event
ToggleFrameStats, ToggleSAPortOrder, ExitGame,
// add new events from here to avoid that user remapped events get overwritten
SettingDecrease, SettingIncrease, PreviousSetting, NextSetting,
+ ToggleAdaptRefresh,
LastType
};
diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx
index fa87be91d..3817232c6 100644
--- a/src/emucore/EventHandler.cxx
+++ b/src/emucore/EventHandler.cxx
@@ -350,17 +350,25 @@ AdjustFunction EventHandler::cycleAdjustSetting(int direction)
myOSystem.settings().getString("palette") == PaletteHandler::SETTING_CUSTOM;
const bool isCustomFilter =
myOSystem.settings().getInt("tv.filter") == int(NTSCFilter::Preset::CUSTOM);
+ bool repeat;
do
{
myAdjustSetting =
AdjustSetting(BSPF::clampw(int(myAdjustSetting) + direction, 0, int(AdjustSetting::MAX_ADJ)));
// skip currently non-relevant adjustments
- } while((myAdjustSetting == AdjustSetting::OVERSCAN && !isFullScreen)
- || (myAdjustSetting == AdjustSetting::PALETTE_PHASE && !isCustomPalette)
- || (myAdjustSetting >= AdjustSetting::NTSC_SHARPNESS
- && myAdjustSetting <= AdjustSetting::NTSC_BLEEDING
- && !isCustomFilter));
+ repeat = (myAdjustSetting == AdjustSetting::OVERSCAN && !isFullScreen)
+ #ifdef ADAPTABLE_REFRESH_SUPPORT
+ || (myAdjustSetting == AdjustSetting::ADAPT_REFRESH && !isFullScreen)
+ #endif
+ || (myAdjustSetting == AdjustSetting::PALETTE_PHASE && !isCustomPalette)
+ || (myAdjustSetting >= AdjustSetting::NTSC_SHARPNESS
+ && myAdjustSetting <= AdjustSetting::NTSC_BLEEDING
+ && !isCustomFilter);
+ // avoid endless loop
+ if(repeat && !direction)
+ direction = 1;
+ } while(repeat);
return getAdjustSetting(myAdjustSetting);
}
@@ -376,6 +384,9 @@ AdjustFunction EventHandler::getAdjustSetting(AdjustSetting setting)
std::bind(&Sound::adjustVolume, &myOSystem.sound(), _1),
std::bind(&FrameBuffer::selectVidMode, &myOSystem.frameBuffer(), _1),
std::bind(&FrameBuffer::toggleFullscreen, &myOSystem.frameBuffer(), _1),
+ #ifdef ADAPTABLE_REFRESH_SUPPORT
+ std::bind(&FrameBuffer::toggleAdaptRefresh, &myOSystem.frameBuffer(), _1),
+ #endif
std::bind(&FrameBuffer::changeOverscan, &myOSystem.frameBuffer(), _1),
std::bind(&Console::selectFormat, &myOSystem.console(), _1),
std::bind(&Console::changeVerticalCenter, &myOSystem.console(), _1),
@@ -658,6 +669,17 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated)
}
return;
+ #ifdef ADAPTABLE_REFRESH_SUPPORT
+ case Event::ToggleAdaptRefresh:
+ if(pressed && !repeated)
+ {
+ myOSystem.frameBuffer().toggleAdaptRefresh();
+ myAdjustSetting = AdjustSetting::ADAPT_REFRESH;
+ myAdjustActive = true;
+ }
+ return;
+ #endif
+
case Event::OverscanDecrease:
if(pressed)
{
@@ -2218,6 +2240,9 @@ EventHandler::EmulActionList EventHandler::ourEmulActionList = { {
{ Event::KeyboardOnePound, "P1 Keyboard #", "" },
// Video
{ Event::ToggleFullScreen, "Toggle fullscreen", "" },
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+ { Event::ToggleAdaptRefresh, "Toggle fullscreen refresh rate adapt", "" },
+#endif
{ Event::OverscanDecrease, "Decrease overscan in fullscreen mode", "" },
{ Event::OverscanIncrease, "Increase overscan in fullscreen mode", "" },
{ Event::VidmodeDecrease, "Previous zoom level", "" },
@@ -2361,7 +2386,7 @@ const Event::EventSet EventHandler::MiscEvents = {
const Event::EventSet EventHandler::AudioVideoEvents = {
Event::VolumeDecrease, Event::VolumeIncrease, Event::SoundToggle,
Event::VidmodeDecrease, Event::VidmodeIncrease,
- Event::ToggleFullScreen,
+ Event::ToggleFullScreen, Event::ToggleAdaptRefresh,
Event::OverscanDecrease, Event::OverscanIncrease,
Event::FormatDecrease, Event::FormatIncrease,
Event::VCenterDecrease, Event::VCenterIncrease,
diff --git a/src/emucore/EventHandler.hxx b/src/emucore/EventHandler.hxx
index 0ab40c7cd..7d9dd235e 100644
--- a/src/emucore/EventHandler.hxx
+++ b/src/emucore/EventHandler.hxx
@@ -398,6 +398,9 @@ class EventHandler
VOLUME,
ZOOM,
FULLSCREEN,
+ #ifdef ADAPTABLE_REFRESH_SUPPORT
+ ADAPT_REFRESH,
+ #endif
OVERSCAN,
TVFORMAT,
VCENTER,
@@ -517,7 +520,12 @@ class EventHandler
#else
PNG_SIZE = 0,
#endif
- EMUL_ACTIONLIST_SIZE = 156 + PNG_SIZE + COMBO_SIZE,
+ #ifdef ADAPTABLE_REFRESH_SUPPORT
+ REFRESH_SIZE = 1,
+ #else
+ REFRESH_SIZE = 0,
+ #endif
+ EMUL_ACTIONLIST_SIZE = 156 + PNG_SIZE + COMBO_SIZE + REFRESH_SIZE,
MENU_ACTIONLIST_SIZE = 18
;
diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx
index 4599a3860..4812e22f6 100644
--- a/src/emucore/FrameBuffer.cxx
+++ b/src/emucore/FrameBuffer.cxx
@@ -959,6 +959,7 @@ void FrameBuffer::setFullscreen(bool enable)
default:
return;
}
+ saveCurrentWindowPosition();
// Changing the video mode can take some time, during which the last
// sound played may get 'stuck'
@@ -993,9 +994,49 @@ void FrameBuffer::toggleFullscreen(bool toggle)
setFullscreen(isFullscreen);
- showMessage(string("Fullscreen ") + (isFullscreen ? "enabled" : "disabled"));
+ if(myBufferType == BufferType::Emulator)
+ {
+ ostringstream msg;
+
+ msg << "Fullscreen ";
+ if(isFullscreen)
+ msg << "enabled (" << refreshRate() << " Hz)";
+ else
+ msg << "disabled";
+
+ showMessage(msg.str());
+ }
}
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+void FrameBuffer::toggleAdaptRefresh(bool toggle)
+{
+ bool isAdaptRefresh = myOSystem.settings().getInt("tia.fs_refresh");
+
+ if(toggle)
+ isAdaptRefresh = !isAdaptRefresh;
+
+ if(myBufferType == BufferType::Emulator)
+ {
+ if(toggle)
+ {
+ myOSystem.settings().setValue("tia.fs_refresh", isAdaptRefresh);
+ // issue a complete framebuffer re-initialization
+ myOSystem.createFrameBuffer();
+ }
+
+ ostringstream msg;
+
+ msg << "Adapt refresh rate ";
+ msg << (isAdaptRefresh ? "enabled" : "disabled");
+ msg << " (" << refreshRate() << " Hz)";
+
+ showMessage(msg.str());
+ }
+}
+#endif
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBuffer::changeOverscan(int direction)
{
diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx
index 3912f38cd..a86743911 100644
--- a/src/emucore/FrameBuffer.hxx
+++ b/src/emucore/FrameBuffer.hxx
@@ -81,6 +81,13 @@ class FrameBuffer
}
};
+ struct DisplayMode
+ {
+ uInt32 display;
+ Common::Size size;
+ uInt32 refresh_rate;
+ };
+
enum class BufferType {
None,
Launcher,
@@ -262,6 +269,13 @@ class FrameBuffer
*/
void toggleFullscreen(bool toggle = true);
+ #ifdef ADAPTABLE_REFRESH_SUPPORT
+ /**
+ Toggles between adapt fullscreen refresh rate on and off.
+ */
+ void FrameBuffer::toggleAdaptRefresh(bool toggle = true);
+ #endif
+
/**
Changes the fullscreen overscan.
@@ -439,6 +453,7 @@ class FrameBuffer
virtual int scaleY(int y) const { return y; }
protected:
+
/**
This method is called to query and initialize the video hardware
for desktop and fullscreen resolution information. Since several
@@ -510,6 +525,11 @@ class FrameBuffer
*/
virtual string about() const = 0;
+ /**
+ Retrieve the current display's refresh rate
+ */
+ virtual int refreshRate() const { return 0; }
+
protected:
// The parent system for the framebuffer
OSystem& myOSystem;
diff --git a/src/emucore/Settings.cxx b/src/emucore/Settings.cxx
index f5e120408..790c784bb 100644
--- a/src/emucore/Settings.cxx
+++ b/src/emucore/Settings.cxx
@@ -52,6 +52,7 @@ Settings::Settings()
setPermanent("tia.zoom", "3");
setPermanent("fullscreen", "false");
setPermanent("tia.fs_stretch", "false");
+ setPermanent("tia.fs_refresh", "false");
setPermanent("tia.fs_overscan", "0");
setPermanent("tia.vsizeadjust", 0);
setPermanent("tia.dbgcolors", "roygpb");
@@ -441,6 +442,7 @@ void Settings::usage() const
<< " -tia.inter <1|0> Enable interpolated (smooth) scaling for TIA\n"
<< " image\n"
<< " -tia.fs_stretch <1|0> Stretch TIA image to fill fullscreen mode\n"
+ << " -tia.fs_refresh <1|0> Try to adapt display refresh rate to game's FPS\n"
<< " -tia.fs_overscan <0-10> Add overscan to TIA image in fullscreen mode\n"
<< " -tia.dbgcolors Debug colors to use for each object (see manual\n"
<< " for description)\n"
diff --git a/src/gui/VideoAudioDialog.cxx b/src/gui/VideoAudioDialog.cxx
index 48fdcc8de..c51685fcf 100644
--- a/src/gui/VideoAudioDialog.cxx
+++ b/src/gui/VideoAudioDialog.cxx
@@ -83,14 +83,6 @@ VideoAudioDialog::VideoAudioDialog(OSystem& osystem, DialogContainer& parent,
addTVEffectsTab();
addAudioTab();
- //const int req_w = std::max(myFastSCBios->getRight(), myCloneBad->getRight()) + HBORDER + 1;
- //const int req_h = _th + VGAP * 3
- // + std::max(myUseVSync->getBottom(), myTVScanIntense->getBottom())
- // + buttonHeight + VBORDER * 2;
- //// Set real dimensions
- //setSize(req_w, req_h, max_w, max_h);
-
-
// Add Defaults, OK and Cancel buttons
WidgetArray wid;
addDefaultsOKCancelBGroup(wid, _font);
@@ -149,26 +141,29 @@ void VideoAudioDialog::addDisplayTab()
wid.push_back(myFullscreen);
ypos += lineHeight + VGAP;
- /*pwidth = font.getStringWidth("0: 3840x2860@120Hz");
- myFullScreenMode = new PopUpWidget(myTab, font, xpos + INDENT + 2, ypos, pwidth, lineHeight,
- instance().frameBuffer().supportedScreenModes(), "Mode ");
- wid.push_back(myFullScreenMode);
- ypos += lineHeight + VGAP;*/
-
// FS stretch
myUseStretch = new CheckboxWidget(myTab, _font, xpos + INDENT, ypos + 1, "Stretch");
wid.push_back(myUseStretch);
+
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+ // Adapt refresh rate
ypos += lineHeight + VGAP;
+ myRefreshAdapt = new CheckboxWidget(myTab, _font, xpos + INDENT, ypos + 1, "Adapt display refresh rate");
+ wid.push_back(myRefreshAdapt);
+#else
+ myRefreshAdapt = nullptr;
+#endif
// FS overscan
+ ypos += lineHeight + VGAP;
myTVOverscan = new SliderWidget(myTab, _font, xpos + INDENT, ypos - 1, swidth, lineHeight,
"Overscan", lwidth - INDENT, kOverscanChanged, fontWidth * 3, "%");
myTVOverscan->setMinValue(0); myTVOverscan->setMaxValue(10);
myTVOverscan->setTickmarkIntervals(2);
wid.push_back(myTVOverscan);
- ypos += lineHeight + VGAP;
// Vertical size
+ ypos += lineHeight + VGAP;
myVSizeAdjust =
new SliderWidget(myTab, _font, xpos, ypos-1, swidth, lineHeight,
"V-Size adjust", lwidth, kVSizeChanged, fontWidth * 7, "%", 0, true);
@@ -176,6 +171,7 @@ void VideoAudioDialog::addDisplayTab()
myVSizeAdjust->setTickmarkIntervals(2);
wid.push_back(myVSizeAdjust);
+
// Add items for tab 0
addToFocusList(wid, myTab, tabID);
}
@@ -480,10 +476,12 @@ void VideoAudioDialog::loadConfig()
// Fullscreen
myFullscreen->setState(instance().settings().getBool("fullscreen"));
- /*string mode = instance().settings().getString("fullscreenmode");
- myFullScreenMode->setSelected(mode);*/
// Fullscreen stretch setting
myUseStretch->setState(instance().settings().getBool("tia.fs_stretch"));
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+ // Adapt refresh rate
+ myRefreshAdapt->setState(instance().settings().getBool("tia.fs_refresh"));
+#endif
// Fullscreen overscan setting
myTVOverscan->setValue(instance().settings().getInt("tia.fs_overscan"));
handleFullScreenChange();
@@ -595,6 +593,10 @@ void VideoAudioDialog::saveConfig()
instance().settings().setValue("fullscreen", myFullscreen->getState());
// Fullscreen stretch setting
instance().settings().setValue("tia.fs_stretch", myUseStretch->getState());
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+ // Adapt refresh rate
+ instance().settings().setValue("tia.fs_refresh", myRefreshAdapt->getState());
+#endif
// Fullscreen overscan
instance().settings().setValue("tia.fs_overscan", myTVOverscan->getValueLabel());
@@ -611,7 +613,6 @@ void VideoAudioDialog::saveConfig()
// Note: Palette values are saved directly when changed!
-
/////////////////////////////////////////////////////////////////////////////
// TV Effects tab
// TV Mode
@@ -706,8 +707,10 @@ void VideoAudioDialog::setDefaults()
myTIAInterpolate->setState(false);
// screen size
myFullscreen->setState(false);
- //myFullScreenMode->setSelectedIndex(0);
myUseStretch->setState(false);
+ #ifdef ADAPTABLE_REFRESH_SUPPORT
+ myRefreshAdapt->setState(false);
+ #endif
myTVOverscan->setValue(0);
myTIAZoom->setValue(300);
myVSizeAdjust->setValue(0);
@@ -833,6 +836,9 @@ void VideoAudioDialog::handleFullScreenChange()
{
bool enable = myFullscreen->getState();
myUseStretch->setEnabled(enable);
+#ifdef ADAPTABLE_REFRESH_SUPPORT
+ myRefreshAdapt->setEnabled(enable);
+#endif
myTVOverscan->setEnabled(enable);
}
diff --git a/src/gui/VideoAudioDialog.hxx b/src/gui/VideoAudioDialog.hxx
index f46740ae6..0961f776e 100644
--- a/src/gui/VideoAudioDialog.hxx
+++ b/src/gui/VideoAudioDialog.hxx
@@ -71,9 +71,9 @@ class VideoAudioDialog : public Dialog
PopUpWidget* myRenderer{nullptr};
CheckboxWidget* myTIAInterpolate{nullptr};
CheckboxWidget* myFullscreen{nullptr};
- //PopUpWidget* myFullScreenMode;
CheckboxWidget* myUseStretch{nullptr};
SliderWidget* myTVOverscan{nullptr};
+ CheckboxWidget* myRefreshAdapt{nullptr};
SliderWidget* myTIAZoom{nullptr};
SliderWidget* myVSizeAdjust{nullptr};