From e15d27dc964f546747f7b7d472d1f1d93b331828 Mon Sep 17 00:00:00 2001 From: Stephen Anthony Date: Tue, 13 Oct 2020 10:47:37 -0230 Subject: [PATCH] Started cleanup of FrameBuffer class. - Moved Video mode stuff into separate class - Fix bug with aspect correction in fullscreen mode still giving graphical artifacts --- src/common/FrameBufferSDL2.cxx | 3 +- src/common/FrameBufferSDL2.hxx | 3 +- src/common/Version.hxx | 2 +- src/common/VideoModeHandler.cxx | 164 ++++++++++ src/common/VideoModeHandler.hxx | 111 +++++++ src/common/module.mk | 15 +- src/emucore/Console.cxx | 4 + src/emucore/EventHandler.cxx | 6 +- src/emucore/FrameBuffer.cxx | 443 +++++++-------------------- src/emucore/FrameBuffer.hxx | 142 +++------ src/emucore/TIASurface.cxx | 2 +- src/emucore/TIASurface.hxx | 2 +- src/libretro/FrameBufferLIBRETRO.hxx | 3 +- src/libretro/Makefile.common | 1 + 14 files changed, 439 insertions(+), 462 deletions(-) create mode 100644 src/common/VideoModeHandler.cxx create mode 100644 src/common/VideoModeHandler.hxx diff --git a/src/common/FrameBufferSDL2.cxx b/src/common/FrameBufferSDL2.cxx index 24b038d01..98f3ea36c 100644 --- a/src/common/FrameBufferSDL2.cxx +++ b/src/common/FrameBufferSDL2.cxx @@ -225,7 +225,8 @@ Int32 FrameBufferSDL2::getCurrentDisplayIndex() const } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode) +bool FrameBufferSDL2::activateVideoMode(const string& title, + const VideoModeHandler::Mode& mode) { ASSERT_MAIN_THREAD; diff --git a/src/common/FrameBufferSDL2.hxx b/src/common/FrameBufferSDL2.hxx index 0b3386d08..20ae489e4 100644 --- a/src/common/FrameBufferSDL2.hxx +++ b/src/common/FrameBufferSDL2.hxx @@ -179,7 +179,8 @@ class FrameBufferSDL2 : public FrameBuffer @return False on any errors, else true */ - bool setVideoMode(const string& title, const VideoMode& mode) override; + bool activateVideoMode(const string& title, + const VideoModeHandler::Mode& mode) override; /** Checks if the display refresh rate should be adapted to game refresh rate in (real) fullscreen mode diff --git a/src/common/Version.hxx b/src/common/Version.hxx index e84d6ece3..18bd4c76c 100644 --- a/src/common/Version.hxx +++ b/src/common/Version.hxx @@ -18,7 +18,7 @@ #ifndef VERSION_HXX #define VERSION_HXX -#define STELLA_VERSION "6.3" +#define STELLA_VERSION "6.4_pre" #define STELLA_BUILD "6180" #endif diff --git a/src/common/VideoModeHandler.cxx b/src/common/VideoModeHandler.cxx new file mode 100644 index 000000000..259f61caa --- /dev/null +++ b/src/common/VideoModeHandler.cxx @@ -0,0 +1,164 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "Settings.hxx" +#include "VideoModeHandler.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void VideoModeHandler::setImageSize(const Common::Size& image) +{ + myImage = image; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void VideoModeHandler::setDisplaySize(const Common::Size& display, Int32 fsIndex) +{ + myDisplay = display; + myFSIndex = fsIndex; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +const VideoModeHandler::Mode& +VideoModeHandler::buildMode(const Settings& settings, bool inTIAMode) +{ + const bool windowedRequested = myFSIndex == -1; + + // TIA mode allows zooming at non-integral factors in most cases + if(inTIAMode) + { + if(windowedRequested) + { + const float zoom = settings.getFloat("tia.zoom"); + ostringstream desc; + desc << (zoom * 100) << "%"; + + // Image and screen (aka window) dimensions are the same + // Overscan is not applicable in this mode + myMode = Mode(myImage.w * zoom, myImage.h * zoom, Mode::Stretch::Fill, + myFSIndex, desc.str(), zoom); + } + else + { + const float overscan = 1 - settings.getInt("tia.fs_overscan") / 100.0; + + // First calculate maximum zoom that keeps aspect ratio + const float scaleX = float(myImage.w) / myDisplay.w, + scaleY = float(myImage.h) / myDisplay.h; + float zoom = 1.F / std::max(scaleX, scaleY); + + // When aspect ratio correction is off, we want pixel-exact images, + // so we default to integer zooming + if(!settings.getBool("tia.correct_aspect")) + zoom = static_cast(zoom); + + if(!settings.getBool("tia.fs_stretch")) // preserve aspect, use all space + { + myMode = Mode(myImage.w * zoom, myImage.h * zoom, + myDisplay.w, myDisplay.h, + Mode::Stretch::Preserve, myFSIndex, + "Fullscreen: Preserve aspect, no stretch", zoom, overscan); + } + else // ignore aspect, use all space + { + myMode = Mode(myImage.w * zoom, myImage.h * zoom, + myDisplay.w, myDisplay.h, + Mode::Stretch::Fill, myFSIndex, + "Fullscreen: Ignore aspect, full stretch", zoom, overscan); + } + } + } + else // UI mode (no zooming) + { + if(windowedRequested) + myMode = Mode(myImage.w, myImage.h, Mode::Stretch::None); + else + myMode = Mode(myImage.w, myImage.h, myDisplay.w, myDisplay.h, + Mode::Stretch::None, myFSIndex); + } + + return myMode; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, Stretch smode, + Int32 fsindex, const string& desc, + float zoomLevel) + : Mode(iw, ih, iw, ih, smode, fsindex, desc, zoomLevel) +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +VideoModeHandler::Mode::Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, + Stretch smode, Int32 fsindex, const string& desc, + float zoomLevel, float overscan) + : stretch(smode), + description(desc), + zoom(zoomLevel), + fsIndex(fsindex) +{ + // First set default size and positioning + screen = Common::Size(sw, sh); + + // Now resize based on windowed/fullscreen mode and stretch factor + if(fsIndex != -1) // fullscreen mode + { + switch(stretch) + { + case Stretch::Preserve: + { + iw *= overscan; + ih *= overscan; + break; + } + + case Stretch::Fill: + // Scale to all available space + iw = screen.w * overscan; + ih = screen.h * overscan; + break; + + case Stretch::None: + // Don't do any scaling at all + iw = std::min(iw, screen.w) * overscan; + ih = std::min(ih, screen.h) * overscan; + break; + } + } + else + { + // In windowed mode, currently the size is scaled to the screen + // TODO - this may be updated if/when we allow variable-sized windows + switch(stretch) + { + case Stretch::Preserve: + case Stretch::Fill: + screen.w = iw; + screen.h = ih; + break; + case Stretch::None: + break; // Do not change image or screen rects whatsoever + } + } + + // Now re-calculate the dimensions + iw = std::min(iw, screen.w); + ih = std::min(ih, screen.h); + + image.moveTo((screen.w - iw) >> 1, (screen.h - ih) >> 1); + image.setWidth(iw); + image.setHeight(ih); +} diff --git a/src/common/VideoModeHandler.hxx b/src/common/VideoModeHandler.hxx new file mode 100644 index 000000000..b91e34894 --- /dev/null +++ b/src/common/VideoModeHandler.hxx @@ -0,0 +1,111 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef VIDEO_MODE_HANDLER_HXX +#define VIDEO_MODE_HANDLER_HXX + +class Settings; + +#include "Rect.hxx" +#include "bspf.hxx" + +class VideoModeHandler +{ + public: + // Contains all relevant info for the dimensions of a video screen + // Also takes care of the case when the image should be 'centered' + // within the given screen: + // 'image' is the image dimensions into the screen + // 'screen' are the dimensions of the screen itself + struct Mode + { + enum class Stretch { + Preserve, // Stretch to fill all available space; preserve aspect ratio + Fill, // Stretch to fill all available space + None // No stretching (1x zoom) + }; + + Common::Rect image; + Common::Size screen; + Stretch stretch{Mode::Stretch::None}; + string description; + float zoom{1.F}; + Int32 fsIndex{-1}; // -1 indicates windowed mode + + Mode() = default; + Mode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, Stretch smode, + Int32 fsindex = -1, const string& desc = "", + float zoomLevel = 1.F, float overscan = 1.F); + Mode(uInt32 iw, uInt32 ih, Stretch smode, Int32 fsindex = -1, + const string& desc = "", float zoomLevel = 1.F); + + friend ostream& operator<<(ostream& os, const Mode& vm) + { + os << "image=" << vm.image << " screen=" << vm.screen + << " stretch=" << (vm.stretch == Stretch::Preserve ? "preserve" : + vm.stretch == Stretch::Fill ? "fill" : "none") + << " desc=" << vm.description << " zoom=" << vm.zoom + << " fsIndex= " << vm.fsIndex; + return os; + } + }; + + public: + VideoModeHandler() = default; + + /** + Set the base size of the image. Scaling can be applied to this, + which will change the effective size. + + @param image The base dimensions of the image + */ + void setImageSize(const Common::Size& image); + + /** + Set the size of the display. This could be either the desktop size, + or the size of the monitor currently active. + + @param display The dimensions of the enclosing display + @param fsIndex Fullscreen mode in use (-1 indicates windowed mode) + */ + void setDisplaySize(const Common::Size& display, Int32 fsIndex = -1); + + /** + Build a video mode based on the given parameters, assuming that + setImageSize and setDisplaySize have been previously called. + + @param settings Used to query various options that affect video mode + @param inTIAMode Whether the video mode is being used for TIA emulation + + @return A video mode based on the given criteria + */ + const VideoModeHandler::Mode& buildMode(const Settings& settings, bool inTIAMode); + + private: + Common::Size myImage, myDisplay; + Int32 myFSIndex{-1}; + + Mode myMode; + + private: + VideoModeHandler(const VideoModeHandler&) = delete; + VideoModeHandler(VideoModeHandler&&) = delete; + VideoModeHandler& operator=(const VideoModeHandler&) = delete; + VideoModeHandler& operator=(const VideoModeHandler&&) = delete; +}; + +#endif // VIDEO_MODE_HANDLER_HXX diff --git a/src/common/module.mk b/src/common/module.mk index 1aae6cd0a..46248b23e 100644 --- a/src/common/module.mk +++ b/src/common/module.mk @@ -1,9 +1,12 @@ MODULE := src/common MODULE_OBJS := \ + src/common/AudioQueue.o \ + src/common/AudioSettings.o \ src/common/Base.o \ src/common/EventHandlerSDL2.o \ src/common/FBSurfaceSDL2.o \ + src/common/FpsMeter.o \ src/common/FrameBufferSDL2.o \ src/common/FSNodeZIP.o \ src/common/JoyMap.o \ @@ -19,14 +22,12 @@ MODULE_OBJS := \ src/common/PNGLibrary.o \ src/common/RewindManager.o \ src/common/SoundSDL2.o \ - src/common/StateManager.o \ - src/common/TimerManager.o \ - src/common/ZipHandler.o \ - src/common/AudioQueue.o \ - src/common/AudioSettings.o \ - src/common/FpsMeter.o \ - src/common/ThreadDebugging.o \ src/common/StaggeredLogger.o \ + src/common/StateManager.o \ + src/common/ThreadDebugging.o \ + src/common/TimerManager.o \ + src/common/VideoModeHandler.o \ + src/common/ZipHandler.o \ src/common/repository/KeyValueRepositoryConfigfile.o \ src/common/sdl_blitter/BilinearBlitter.o \ src/common/sdl_blitter/QisBlitter.o \ diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index a0bd2a25a..2d4f0f5a6 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -622,6 +622,10 @@ FBInitStatus Console::initializeVideo(bool full) if(full) { + auto size = myOSystem.settings().getBool("tia.correct_aspect") ? + Common::Size(TIAConstants::viewableWidth, TIAConstants::viewableHeight) : + Common::Size(2 * myTIA->width(), myTIA->height()); + uInt32 width, height; if (!myOSystem.settings().getBool("tia.correct_aspect")) { width = 2 * myTIA->width(); diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx index 788cb73b9..a5b0d5b1f 100644 --- a/src/emucore/EventHandler.cxx +++ b/src/emucore/EventHandler.cxx @@ -411,7 +411,7 @@ AdjustFunction EventHandler::getAdjustSetting(AdjustSetting setting) { // Audio & Video settings std::bind(&Sound::adjustVolume, &myOSystem.sound(), _1), - std::bind(&FrameBuffer::selectVidMode, &myOSystem.frameBuffer(), _1), + std::bind(&FrameBuffer::switchVideoMode, &myOSystem.frameBuffer(), _1), std::bind(&FrameBuffer::toggleFullscreen, &myOSystem.frameBuffer(), _1), #ifdef ADAPTABLE_REFRESH_SUPPORT std::bind(&FrameBuffer::toggleAdaptRefresh, &myOSystem.frameBuffer(), _1), @@ -693,7 +693,7 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated) case Event::VidmodeDecrease: if(pressed) { - myOSystem.frameBuffer().selectVidMode(-1); + myOSystem.frameBuffer().switchVideoMode(-1); myAdjustSetting = AdjustSetting::ZOOM; myAdjustActive = true; } @@ -702,7 +702,7 @@ void EventHandler::handleEvent(Event::Type event, Int32 value, bool repeated) case Event::VidmodeIncrease: if(pressed) { - myOSystem.frameBuffer().selectVidMode(+1); + myOSystem.frameBuffer().switchVideoMode(+1); myAdjustSetting = AdjustSetting::ZOOM; myAdjustActive = true; } diff --git a/src/emucore/FrameBuffer.cxx b/src/emucore/FrameBuffer.cxx index c3c9bfe02..420826bf3 100644 --- a/src/emucore/FrameBuffer.cxx +++ b/src/emucore/FrameBuffer.cxx @@ -104,9 +104,11 @@ bool FrameBuffer::initialize() #endif // Determine possible TIA windowed zoom levels - myTIAMaxZoom = maxZoomForScreen( - TIAConstants::viewableWidth, TIAConstants::viewableHeight, - myAbsDesktopSize.w, myAbsDesktopSize.h); + myTIAMaxZoom = maxWindowZoom(TIAConstants::viewableWidth, + TIAConstants::viewableHeight); + float currentTIAZoom = myOSystem.settings().getFloat("tia.zoom"); + myOSystem.settings().setValue("tia.zoom", + BSPF::clampw(currentTIAZoom, supportedTIAMinZoom(), myTIAMaxZoom)); setUIPalette(); @@ -173,13 +175,13 @@ void FrameBuffer::setupFonts() // However, we have to make sure all Dialogs are sized using the fontsize. int zoom_h = (fd.height * 4 * 2) / GUI::stellaMediumDesc.height; int zoom_w = (fd.maxwidth * 4 * 2) / GUI::stellaMediumDesc.maxwidth; - myTIAMinZoom = std::max(std::max(zoom_w, zoom_h) / 4.F, 2.F); // round to 25% steps, >= 200% + // round to 25% steps, >= 200% + myTIAMinZoom = std::max(std::max(zoom_w, zoom_h) / 4.F, 2.F); } // The font used by the ROM launcher const string& lf = myOSystem.settings().getString("launcherfont"); - myLauncherFont = make_unique(getFontDesc(lf)); // 8x13 } @@ -215,7 +217,7 @@ FBInitStatus FrameBuffer::createDisplay(const string& title, BufferType type, ++myInitializedCount; myScreenTitle = title; - // In HiDPI mode, all created displays must be scaled by 2x + // In HiDPI mode, all created displays must be scaled appropriately if(honourHiDPI && hidpiEnabled()) { width *= hidpiScaleFactor(); @@ -232,7 +234,6 @@ FBInitStatus FrameBuffer::createDisplay(const string& title, BufferType type, // If the WINDOWED_SUPPORT macro is defined, we treat the system as the // former type; if not, as the latter type - bool useFullscreen = false; #ifdef WINDOWED_SUPPORT // We assume that a desktop of at least minimum acceptable size means that // we're running on a 'large' system, and the window size requirements @@ -241,40 +242,37 @@ FBInitStatus FrameBuffer::createDisplay(const string& title, BufferType type, if(myDesktopSize.w < FBMinimum::Width && myDesktopSize.h < FBMinimum::Height && (myDesktopSize.w < width || myDesktopSize.h < height)) return FBInitStatus::FailTooLarge; - - useFullscreen = myOSystem.settings().getBool("fullscreen"); #else // Make sure this mode is even possible // We only really need to worry about it in non-windowed environments, // where requesting a window that's too large will probably cause a crash if(myDesktopSize.w < width || myDesktopSize.h < height) return FBInitStatus::FailTooLarge; - - useFullscreen = true; #endif - // Set the available video modes for this framebuffer - setAvailableVidModes(width, height); + // Initialize video mode handler, so it can know what video modes are + // appropriate for this framebuffer + myVidModeHandler.setImageSize(Common::Size(width, height)); // Initialize video subsystem (make sure we get a valid mode) string pre_about = about(); - const FrameBuffer::VideoMode& mode = getSavedVidMode(useFullscreen); - if(width <= mode.screen.w && height <= mode.screen.h) + myActiveVidMode = buildVideoMode(); + if(width <= myActiveVidMode.screen.w && height <= myActiveVidMode.screen.h) { // Changing the video mode can take some time, during which the last // sound played may get 'stuck' // So we mute the sound until the operation completes bool oldMuteState = myOSystem.sound().mute(true); - if(setVideoMode(myScreenTitle, mode)) + if(activateVideoMode(myScreenTitle, myActiveVidMode)) { - myImageRect = mode.image; - myScreenSize = mode.screen; - myScreenRect = Common::Rect(mode.screen); + myImageRect = myActiveVidMode.image; + myScreenSize = myActiveVidMode.screen; + myScreenRect = Common::Rect(myActiveVidMode.screen); // Inform TIA surface about new mode if(myOSystem.eventHandler().state() != EventHandlerState::LAUNCHER && myOSystem.eventHandler().state() != EventHandlerState::DEBUGGER) - myTIASurface->initialize(myOSystem.console(), mode); + myTIASurface->initialize(myOSystem.console(), myActiveVidMode); // Did we get the requested fullscreen state? myOSystem.settings().setValue("fullscreen", fullScreen()); @@ -835,8 +833,9 @@ void FrameBuffer::setPauseDelay() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -shared_ptr FrameBuffer::allocateSurface(int w, int h, ScalingInterpolation interpolation, - const uInt32* data) +shared_ptr FrameBuffer::allocateSurface( + int w, int h, ScalingInterpolation interpolation, const uInt32* data +) { // Add new surface to the list mySurfaceList.push_back(createSurface(w, h, interpolation, data)); @@ -1011,17 +1010,18 @@ void FrameBuffer::setFullscreen(bool enable) // So we mute the sound until the operation completes bool oldMuteState = myOSystem.sound().mute(true); - const VideoMode& mode = getSavedVidMode(enable); - if(setVideoMode(myScreenTitle, mode)) + myOSystem.settings().setValue("fullscreen", enable); + myActiveVidMode = buildVideoMode(); + if(activateVideoMode(myScreenTitle, myActiveVidMode)) { - myImageRect = mode.image; - myScreenSize = mode.screen; - myScreenRect = Common::Rect(mode.screen); + myImageRect = myActiveVidMode.image; + myScreenSize = myActiveVidMode.screen; + myScreenRect = Common::Rect(myActiveVidMode.screen); // Inform TIA surface about new mode if(myOSystem.eventHandler().state() != EventHandlerState::LAUNCHER && myOSystem.eventHandler().state() != EventHandlerState::DEBUGGER) - myTIASurface->initialize(myOSystem.console(), mode); + myTIASurface->initialize(myOSystem.console(), myActiveVidMode); // Did we get the requested fullscreen state? myOSystem.settings().setValue("fullscreen", fullScreen()); @@ -1035,7 +1035,7 @@ void FrameBuffer::setFullscreen(bool enable) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void FrameBuffer::toggleFullscreen(bool toggle) { - switch (myOSystem.eventHandler().state()) + switch(myOSystem.eventHandler().state()) { case EventHandlerState::LAUNCHER: case EventHandlerState::EMULATION: @@ -1043,20 +1043,17 @@ void FrameBuffer::toggleFullscreen(bool toggle) case EventHandlerState::DEBUGGER: { const bool isFullscreen = toggle ? !fullScreen() : fullScreen(); - setFullscreen(isFullscreen); - if (myBufferType != BufferType::Launcher) + if(myBufferType != BufferType::Launcher) { ostringstream msg; - const VideoMode& mode = getSavedVidMode(isFullscreen); - msg << "Fullscreen "; if(isFullscreen) msg << "enabled (" << refreshRate() << " Hz, "; else msg << "disabled ("; - msg << "Zoom " << mode.zoom * 100 << "%)"; + msg << "Zoom " << myActiveVidMode.zoom * 100 << "%)"; showMessage(msg.str()); } @@ -1122,7 +1119,7 @@ void FrameBuffer::changeOverscan(int direction) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::selectVidMode(int direction) +void FrameBuffer::switchVideoMode(int direction) { EventHandlerState state = myOSystem.eventHandler().state(); bool tiaMode = (state != EventHandlerState::DEBUGGER && @@ -1132,10 +1129,27 @@ void FrameBuffer::selectVidMode(int direction) if(!tiaMode) return; - if(direction == +1) - myCurrentModeList->next(); - else if(direction == -1) - myCurrentModeList->previous(); + if(!fullScreen()) + { + // Windowed TIA modes support variable zoom levels + float zoom = myOSystem.settings().getFloat("tia.zoom"); + if(direction == +1) zoom += ZOOM_STEPS; + else if(direction == -1) zoom -= ZOOM_STEPS; + + // Make sure the level is within the allowable desktop size + zoom = BSPF::clampw(zoom, supportedTIAMinZoom(), myTIAMaxZoom); + myOSystem.settings().setValue("tia.zoom", zoom); + } + else + { + // In fullscreen mode, there are only two modes, so direction + // is irrelevant + if(direction == +1 || direction == -1) + { + bool stretch = myOSystem.settings().getBool("tia.fs_stretch"); + myOSystem.settings().setValue("tia.fs_stretch", !stretch); + } + } saveCurrentWindowPosition(); @@ -1144,34 +1158,75 @@ void FrameBuffer::selectVidMode(int direction) // So we mute the sound until the operation completes bool oldMuteState = myOSystem.sound().mute(true); - const VideoMode& mode = myCurrentModeList->current(); - if(setVideoMode(myScreenTitle, mode)) + myActiveVidMode = buildVideoMode(); + if(activateVideoMode(myScreenTitle, myActiveVidMode)) { - myImageRect = mode.image; - myScreenSize = mode.screen; - myScreenRect = Common::Rect(mode.screen); + myImageRect = myActiveVidMode.image; + myScreenSize = myActiveVidMode.screen; + myScreenRect = Common::Rect(myActiveVidMode.screen); // Inform TIA surface about new mode - myTIASurface->initialize(myOSystem.console(), mode); + myTIASurface->initialize(myOSystem.console(), myActiveVidMode); resetSurfaces(); if(fullScreen()) - showMessage(mode.description); + showMessage(myActiveVidMode.description); else - showMessage("Zoom", mode.description, mode.zoom, supportedTIAMinZoom(), myTIAMaxZoom); + showMessage("Zoom", myActiveVidMode.description, myActiveVidMode.zoom, + supportedTIAMinZoom(), myTIAMaxZoom); myOSystem.sound().mute(oldMuteState); + // Error check: were the settings applied as requested? if(fullScreen()) myOSystem.settings().setValue("tia.fs_stretch", - mode.stretch == VideoMode::Stretch::Fill); + myActiveVidMode.stretch == VideoModeHandler::Mode::Stretch::Fill); else - myOSystem.settings().setValue("tia.zoom", mode.zoom); + myOSystem.settings().setValue("tia.zoom", myActiveVidMode.zoom); return; } myOSystem.sound().mute(oldMuteState); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +const VideoModeHandler::Mode& FrameBuffer::buildVideoMode() +{ + // Update display size, in case windowed/fullscreen mode has changed + const Settings& s = myOSystem.settings(); + if(s.getBool("fullscreen")) + { + Int32 fsIndex = std::max(getCurrentDisplayIndex(), 0); + myVidModeHandler.setDisplaySize(myFullscreenDisplays[fsIndex], fsIndex); + } + else + myVidModeHandler.setDisplaySize(myAbsDesktopSize); + + // And now build the new mode based on current settings + const bool tiaMode = + myOSystem.eventHandler().state() != EventHandlerState::DEBUGGER && + myOSystem.eventHandler().state() != EventHandlerState::LAUNCHER; + + return myVidModeHandler.buildMode(s, tiaMode); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +float FrameBuffer::maxWindowZoom(uInt32 baseWidth, uInt32 baseHeight) const +{ + float multiplier = 1; + for(;;) + { + // Figure out the zoomed size of the window + uInt32 width = baseWidth * multiplier; + uInt32 height = baseHeight * multiplier; + + if((width > myAbsDesktopSize.w) || (height > myAbsDesktopSize.h)) + break; + + multiplier += ZOOM_STEPS; + } + return multiplier > 1 ? multiplier - ZOOM_STEPS : 1; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void FrameBuffer::setCursorState() { @@ -1234,296 +1289,6 @@ void FrameBuffer::toggleGrabMouse() : "Grab mouse not allowed while cursor shown"); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -float FrameBuffer::maxZoomForScreen(uInt32 baseWidth, uInt32 baseHeight, - uInt32 screenWidth, uInt32 screenHeight) const -{ - float multiplier = 1; - for(;;) - { - // Figure out the zoomed size of the window - uInt32 width = baseWidth * multiplier; - uInt32 height = baseHeight * multiplier; - - if((width > screenWidth) || (height > screenHeight)) - break; - - multiplier += ZOOM_STEPS; - } - return multiplier > 1 ? multiplier - ZOOM_STEPS : 1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::setAvailableVidModes(uInt32 baseWidth, uInt32 baseHeight) -{ - myWindowedModeList.clear(); - - for(auto& mode: myFullscreenModeLists) - mode.clear(); - for(size_t i = myFullscreenModeLists.size(); i < myFullscreenDisplays.size(); ++i) - myFullscreenModeLists.emplace_back(VideoModeList()); - - // Check if zooming is allowed for this state (currently only allowed - // for TIA screens) - EventHandlerState state = myOSystem.eventHandler().state(); - bool tiaMode = (state != EventHandlerState::DEBUGGER && - state != EventHandlerState::LAUNCHER); - float overscan = 1 - myOSystem.settings().getInt("tia.fs_overscan") / 100.0; - - // TIA mode allows zooming at integral factors in windowed modes, - // and also non-integral factors in fullscreen mode - if(tiaMode) - { - // TIA windowed modes - float minZoom = supportedTIAMinZoom(); - myTIAMaxZoom = maxZoomForScreen(baseWidth, baseHeight, - myAbsDesktopSize.w, myAbsDesktopSize.h); - // Determine all zoom levels - for(float zoom = minZoom; zoom <= myTIAMaxZoom; zoom += ZOOM_STEPS) - { - ostringstream desc; - desc << (zoom * 100) << "%"; - - VideoMode mode(baseWidth*zoom, baseHeight*zoom, baseWidth*zoom, baseHeight*zoom, - VideoMode::Stretch::Fill, 1.0, desc.str(), zoom); - myWindowedModeList.add(mode); - } - - // TIA fullscreen mode - for(uInt32 i = 0; i < myFullscreenDisplays.size(); ++i) - { - myTIAMaxZoom = maxZoomForScreen(baseWidth, baseHeight, - myFullscreenDisplays[i].w * overscan, - myFullscreenDisplays[i].h * overscan); - - // Add both normal aspect and filled modes - // It's easier to define them both now, and simply switch between - // them when necessary - VideoMode mode1(baseWidth * myTIAMaxZoom, baseHeight * myTIAMaxZoom, - myFullscreenDisplays[i].w, myFullscreenDisplays[i].h, - VideoMode::Stretch::Preserve, overscan, - "Fullscreen: Preserve aspect, no stretch", myTIAMaxZoom, i); - myFullscreenModeLists[i].add(mode1); - VideoMode mode2(baseWidth * myTIAMaxZoom, baseHeight * myTIAMaxZoom, - myFullscreenDisplays[i].w, myFullscreenDisplays[i].h, - VideoMode::Stretch::Fill, overscan, - "Fullscreen: Ignore aspect, full stretch", myTIAMaxZoom, i); - myFullscreenModeLists[i].add(mode2); - } - } - else // UI mode - { - // Windowed and fullscreen mode differ only in screen size - myWindowedModeList.add( - VideoMode(baseWidth, baseHeight, baseWidth, baseHeight, - VideoMode::Stretch::None) - ); - for(uInt32 i = 0; i < myFullscreenDisplays.size(); ++i) - { - myFullscreenModeLists[i].add( - VideoMode(baseWidth, baseHeight, - myFullscreenDisplays[i].w, myFullscreenDisplays[i].h, - VideoMode::Stretch::None, 1.0, "", 1, i) - ); - } - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const FrameBuffer::VideoMode& FrameBuffer::getSavedVidMode(bool fullscreen) -{ - if(fullscreen) - { - Int32 i = getCurrentDisplayIndex(); - if(i < 0) - { - // default to the first display - i = 0; - } - myCurrentModeList = &myFullscreenModeLists[i]; - } - else - myCurrentModeList = &myWindowedModeList; - - // Now select the best resolution depending on the state - // UI modes (launcher and debugger) have only one supported resolution - // so the 'current' one is the only valid one - EventHandlerState state = myOSystem.eventHandler().state(); - if(state == EventHandlerState::DEBUGGER || state == EventHandlerState::LAUNCHER) - myCurrentModeList->setByZoom(1); - else // TIA mode - { - if(fullscreen) - myCurrentModeList->setByStretch(myOSystem.settings().getBool("tia.fs_stretch") - ? VideoMode::Stretch::Fill : VideoMode::Stretch::Preserve); - else - myCurrentModeList->setByZoom(myOSystem.settings().getFloat("tia.zoom")); - } - - return myCurrentModeList->current(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// -// VideoMode implementation -// -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -FrameBuffer::VideoMode::VideoMode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, - Stretch smode, float overscan, const string& desc, - float zoomLevel, Int32 fsindex) - : stretch(smode), - description(desc), - zoom(zoomLevel), - fsIndex(fsindex) -{ - // First set default size and positioning - sw = std::max(sw, TIAConstants::viewableWidth); - sh = std::max(sh, TIAConstants::viewableHeight); - iw = std::min(iw, sw); - ih = std::min(ih, sh); - int ix = (sw - iw) >> 1; - int iy = (sh - ih) >> 1; - image = Common::Rect(ix, iy, ix+iw, iy+ih); - screen = Common::Size(sw, sh); - - // Now resize based on windowed/fullscreen mode and stretch factor - iw = image.w(); - ih = image.h(); - - if(fsIndex != -1) - { - switch(stretch) - { - case Stretch::Preserve: - { - float stretchFactor = 1.0; - float scaleX = float(iw) / screen.w; - float scaleY = float(ih) / screen.h; - - // Scale to all available space, keep aspect correct - if(scaleX > scaleY) - stretchFactor = float(screen.w) / iw; - else - stretchFactor = float(screen.h) / ih; - - iw = uInt32(stretchFactor * iw) * overscan; - ih = uInt32(stretchFactor * ih) * overscan; - break; - } - - case Stretch::Fill: - // Scale to all available space - iw = screen.w * overscan; - ih = screen.h * overscan; - break; - - case Stretch::None: - // Don't do any scaling at all, but obey overscan - iw = std::min(iw, screen.w) * overscan; - ih = std::min(ih, screen.h) * overscan; - break; - } - } - else - { - // In windowed mode, currently the size is scaled to the screen - // TODO - this may be updated if/when we allow variable-sized windows - switch(stretch) - { - case Stretch::Preserve: - case Stretch::Fill: - screen.w = iw; - screen.h = ih; - break; - case Stretch::None: - break; // Do not change image or screen rects whatsoever - } - } - - // Now re-calculate the dimensions - iw = std::min(iw, screen.w); - ih = std::min(ih, screen.h); - - image.moveTo((screen.w - iw) >> 1, (screen.h - ih) >> 1); - image.setWidth(iw); - image.setHeight(ih); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// -// VideoModeList implementation -// -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::VideoModeList::add(const VideoMode& mode) -{ - myModeList.emplace_back(mode); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::VideoModeList::clear() -{ - myModeList.clear(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool FrameBuffer::VideoModeList::empty() const -{ - return myModeList.empty(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 FrameBuffer::VideoModeList::size() const -{ - return uInt32(myModeList.size()); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::VideoModeList::previous() -{ - --myIdx; - if(myIdx < 0) myIdx = int(myModeList.size()) - 1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const FrameBuffer::VideoMode& FrameBuffer::VideoModeList::current() const -{ - return myModeList[myIdx]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::VideoModeList::next() -{ - myIdx = (myIdx + 1) % myModeList.size(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::VideoModeList::setByZoom(float zoom) -{ - for(uInt32 i = 0; i < myModeList.size(); ++i) - { - if(myModeList[i].zoom == zoom) - { - myIdx = i; - return; - } - } - myIdx = 0; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void FrameBuffer::VideoModeList::setByStretch(FrameBuffer::VideoMode::Stretch stretch) -{ - for(uInt32 i = 0; i < myModeList.size(); ++i) - { - if(myModeList[i].stretch == stretch) - { - myIdx = i; - return; - } - } - myIdx = 0; -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /* Palette is defined as follows: diff --git a/src/emucore/FrameBuffer.hxx b/src/emucore/FrameBuffer.hxx index 58d61dfd3..ce9040795 100644 --- a/src/emucore/FrameBuffer.hxx +++ b/src/emucore/FrameBuffer.hxx @@ -35,6 +35,7 @@ class TIASurface; #include "TIAConstants.hxx" #include "FrameBufferConstants.hxx" #include "EventHandlerConstants.hxx" +#include "VideoModeHandler.hxx" #include "bspf.hxx" /** @@ -50,38 +51,7 @@ class TIASurface; class FrameBuffer { public: - // Contains all relevant info for the dimensions of a video screen - // Also takes care of the case when the image should be 'centered' - // within the given screen: - // 'image' is the image dimensions into the screen - // 'screen' are the dimensions of the screen itself - struct VideoMode - { - enum class Stretch { Preserve, Fill, None }; - - Common::Rect image; - Common::Size screen; - Stretch stretch{VideoMode::Stretch::None}; - string description; - float zoom{1.F}; - Int32 fsIndex{-1}; - - VideoMode(uInt32 iw, uInt32 ih, uInt32 sw, uInt32 sh, - Stretch smode, float overscan = 1.F, - const string& desc = "", float zoomLevel = 1, Int32 fsindex = -1); - - friend ostream& operator<<(ostream& os, const VideoMode& vm) - { - os << "image=" << vm.image << " screen=" << vm.screen - << " stretch=" << (vm.stretch == Stretch::Preserve ? "preserve" : - vm.stretch == Stretch::Fill ? "fill" : "none") - << " desc=" << vm.description << " zoom=" << vm.zoom - << " fsIndex= " << vm.fsIndex; - return os; - } - }; - - struct DisplayMode + struct DisplayMode // FIXME - is this needed? { uInt32 display; Common::Size size; @@ -259,11 +229,6 @@ class FrameBuffer */ TIASurface& tiaSurface() const { return *myTIASurface; } - /** - Enables/disables fullscreen mode. - */ - void setFullscreen(bool enable); - /** Toggles between fullscreen and window mode. */ @@ -285,15 +250,15 @@ class FrameBuffer /** This method is called when the user wants to switch to the next - available video mode. In windowed mode, this typically means going to - the next/previous zoom level. In fullscreen mode, this typically means - switching between normal aspect and fully filling the screen. + available TIA video mode. In windowed mode, this typically means going + to the next/previous zoom level. In fullscreen mode, this typically + means switching between normal aspect and fully filling the screen. direction = -1 means go to the next lower video mode direction = +1 means go to the next higher video mode @param direction +1 indicates increase, -1 indicates decrease. */ - void selectVidMode(int direction = +1); + void switchVideoMode(int direction = +1); /** Sets the state of the cursor (hidden or grabbed) based on the @@ -453,7 +418,6 @@ 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 @@ -475,8 +439,8 @@ class FrameBuffer @return False on any errors, else true */ - virtual bool setVideoMode(const string& title, - const FrameBuffer::VideoMode& mode) = 0; + virtual bool activateVideoMode(const string& title, + const VideoModeHandler::Mode& mode) = 0; /** This method is called to create a surface with the given attributes. @@ -547,6 +511,28 @@ class FrameBuffer */ bool drawMessage(); + // Draws the frame stats overlay + void drawFrameStats(float framesPerSecond); + + /** + Build an applicable video mode based on the current settings in + effect, whether TIA mode is active, etc. + Note that this only creates the video mode definition itself; + to apply it, we need to call 'activateVideoMode()'. + */ + const VideoModeHandler::Mode& buildVideoMode(); + + /** + Calculate the maximum level by which the base window can be zoomed and + still fit in the desktop screen. + */ + float maxWindowZoom(uInt32 baseWidth, uInt32 baseHeight) const; + + /** + Enables/disables fullscreen mode. + */ + void setFullscreen(bool enable); + /** Frees and reloads all surfaces that the framebuffer knows about. */ @@ -559,60 +545,6 @@ class FrameBuffer void setupFonts(); #endif - /** - Calculate the maximum level by which the base window can be zoomed and - still fit in the given screen dimensions. - */ - float maxZoomForScreen(uInt32 baseWidth, uInt32 baseHeight, - uInt32 screenWidth, uInt32 screenHeight) const; - - /** - Set all possible video modes (both windowed and fullscreen) available for - this framebuffer based on given image dimensions and maximum window size. - */ - void setAvailableVidModes(uInt32 basewidth, uInt32 baseheight); - - /** - Returns an appropriate video mode based on the current eventhandler - state, taking into account the maximum size of the window. - - @param fullscreen Whether to use a windowed or fullscreen mode - @return A valid VideoMode for this framebuffer - */ - const FrameBuffer::VideoMode& getSavedVidMode(bool fullscreen); - - private: - /** - This class implements an iterator around an array of VideoMode objects. - */ - class VideoModeList - { - public: - void add(const FrameBuffer::VideoMode& mode); - void clear(); - - bool empty() const; - uInt32 size() const; - - void previous(); - const FrameBuffer::VideoMode& current() const; - void next(); - - void setByZoom(float zoom); - void setByStretch(FrameBuffer::VideoMode::Stretch stretch); - - friend ostream& operator<<(ostream& os, const VideoModeList& l) - { - for(const auto& vm: l.myModeList) - os << "-----\n" << vm << endl << "-----\n"; - return os; - } - - private: - vector myModeList; - int myIdx{-1}; - }; - protected: // Title of the main window/screen string myScreenTitle; @@ -629,9 +561,6 @@ class FrameBuffer vector myFullscreenDisplays; private: - // Draws the frame stats overlay - void drawFrameStats(float framesPerSecond); - // Indicates the number of times the framebuffer was initialized uInt32 myInitializedCount{0}; @@ -659,6 +588,10 @@ class FrameBuffer // Supported renderers VariantList myRenderers; + // The VideoModeHandler class takes responsibility for all video mode functionality + VideoModeHandler myVidModeHandler; + VideoModeHandler::Mode myActiveVidMode; + #ifdef GUI_SUPPORT // The font object to use for the normal in-game GUI unique_ptr myFont; @@ -699,11 +632,6 @@ class FrameBuffer bool myHiDPIAllowed{false}; bool myHiDPIEnabled{false}; - // The list of all available video modes for this framebuffer - VideoModeList* myCurrentModeList{nullptr}; - VideoModeList myWindowedModeList; - vector myFullscreenModeLists; - // Minimum TIA zoom level that can be used for this framebuffer float myTIAMinZoom{2.F}; // Maximum TIA zoom level that can be used for this framebuffer @@ -714,7 +642,7 @@ class FrameBuffer FullPaletteArray myFullPalette; // Holds UI palette data (for each variation) - static UIPaletteArray ourStandardUIPalette, ourClassicUIPalette, + static UIPaletteArray ourStandardUIPalette, ourClassicUIPalette, ourLightUIPalette, ourDarkUIPalette; private: diff --git a/src/emucore/TIASurface.cxx b/src/emucore/TIASurface.cxx index 2a2cd0327..f3f4018d1 100644 --- a/src/emucore/TIASurface.cxx +++ b/src/emucore/TIASurface.cxx @@ -91,7 +91,7 @@ TIASurface::~TIASurface() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TIASurface::initialize(const Console& console, - const FrameBuffer::VideoMode& mode) + const VideoModeHandler::Mode& mode) { myTIA = &(console.tia()); diff --git a/src/emucore/TIASurface.hxx b/src/emucore/TIASurface.hxx index dd38b2de5..4c7a710d8 100644 --- a/src/emucore/TIASurface.hxx +++ b/src/emucore/TIASurface.hxx @@ -54,7 +54,7 @@ class TIASurface /** Set the TIA object, which is needed for actually rendering the TIA image. */ - void initialize(const Console& console, const FrameBuffer::VideoMode& mode); + void initialize(const Console& console, const VideoModeHandler::Mode& mode); /** Set the palette for TIA rendering. This currently consists of two diff --git a/src/libretro/FrameBufferLIBRETRO.hxx b/src/libretro/FrameBufferLIBRETRO.hxx index a57a218d2..8d097a25e 100644 --- a/src/libretro/FrameBufferLIBRETRO.hxx +++ b/src/libretro/FrameBufferLIBRETRO.hxx @@ -147,7 +147,8 @@ class FrameBufferLIBRETRO : public FrameBuffer @return False on any errors, else true */ - bool setVideoMode(const string& title, const VideoMode& mode) override { return true; } + bool activateVideoMode(const string& title, + const VideoModeHandler::Mode& mode) override { return true; } /** This method is called to create a surface with the given attributes. diff --git a/src/libretro/Makefile.common b/src/libretro/Makefile.common index 8f2bbf81a..ba98f0825 100644 --- a/src/libretro/Makefile.common +++ b/src/libretro/Makefile.common @@ -33,6 +33,7 @@ SOURCES_CXX := \ $(CORE_DIR)/common/StaggeredLogger.cxx \ $(CORE_DIR)/common/StateManager.cxx \ $(CORE_DIR)/common/TimerManager.cxx \ + $(CORE_DIR)/common/VideoModeHandler.cxx \ $(CORE_DIR)/common/tv_filters/AtariNTSC.cxx \ $(CORE_DIR)/common/tv_filters/NTSCFilter.cxx \ $(CORE_DIR)/emucore/AtariVox.cxx \