From db2c4b48d2af89cd68c6bea5b01938dce6a921c2 Mon Sep 17 00:00:00 2001 From: Robert Broglia Date: Sat, 30 Mar 2024 03:14:22 -0400 Subject: [PATCH] Update video options and misc tweaks * Imagine: Add Screen::setVariableFrameTime() and Window::configureFrameTimeSource() to allow changing the screen's active frame timer * Imagine: Add -Wno-vla-extension to warning flags for Clang 18 * EmuFramework: Add "Timer" as a Frame Clock setting for use with VRR displays * EmuFramework: Move functions for text input views out of EmuApp and into a utility header * EmuFramework: Add a new option category "Frame Timing" and move relevant options from the "Video" category there * EmuFramework: Change the "App Zoom" option to instead control the menu scale * EmuFramework: Rename "Content Zoom" video option to "Content Scale" --- 2600.emu/src/main/EmuMenuViews.cc | 15 +- C64.emu/src/main/EmuMenuViews.cc | 18 +- EmuFramework/build.mk | 1 + .../include/emuframework/AppKeyCode.hh | 2 +- .../include/emuframework/AudioOptionView.hh | 3 +- EmuFramework/include/emuframework/Cheats.hh | 5 +- EmuFramework/include/emuframework/EmuApp.hh | 145 +---- EmuFramework/include/emuframework/EmuAudio.hh | 5 +- .../include/emuframework/EmuOptions.hh | 12 +- .../include/emuframework/EmuVideoLayer.hh | 9 +- .../include/emuframework/EmuViewController.hh | 1 - .../include/emuframework/MainMenuView.hh | 3 - .../include/emuframework/VideoOptionView.hh | 38 +- .../include/emuframework/viewUtils.hh | 173 ++++++ EmuFramework/src/ConfigFile.cc | 12 +- EmuFramework/src/EmuApp.cc | 64 ++- EmuFramework/src/EmuAudio.cc | 10 +- EmuFramework/src/EmuOptions.cc | 17 +- EmuFramework/src/EmuVideoLayer.cc | 11 +- EmuFramework/src/gui/AudioOptionView.cc | 63 +-- EmuFramework/src/gui/AutosaveSlotView.cc | 27 +- EmuFramework/src/gui/Cheats.cc | 2 +- EmuFramework/src/gui/EmuViewController.cc | 23 +- EmuFramework/src/gui/FrameTimingView.cc | 445 +++++++++++++++ EmuFramework/src/gui/FrameTimingView.hh | 62 +++ EmuFramework/src/gui/GUIOptionView.cc | 32 +- EmuFramework/src/gui/InputManagerView.cc | 13 +- EmuFramework/src/gui/MainMenuView.cc | 40 +- EmuFramework/src/gui/SystemActionsView.cc | 11 +- EmuFramework/src/gui/SystemOptionView.cc | 46 +- EmuFramework/src/gui/TouchConfigView.cc | 70 +-- EmuFramework/src/gui/VideoOptionView.cc | 518 ++---------------- GBA.emu/src/main/Cheats.cc | 5 +- GBA.emu/src/main/EmuMenuViews.cc | 17 +- GBC.emu/src/main/Cheats.cc | 13 +- GBC.emu/src/main/EmuMenuViews.cc | 8 +- Lynx.emu/src/main/EmuMenuViews.cc | 4 +- MD.emu/src/main/Cheats.cc | 9 +- MD.emu/src/main/EmuMenuViews.cc | 4 +- MSX.emu/src/main/EmuMenuViews.cc | 17 +- NES.emu/src/main/Cheats.cc | 27 +- NES.emu/src/main/EmuMenuViews.cc | 17 +- PCE.emu/src/main/EmuMenuViews.cc | 13 +- Saturn.emu/src/main/EmuMenuViews.cc | 4 +- Snes9x/src/main/Cheats.cc | 19 +- Snes9x/src/main/EmuMenuViews.cc | 11 +- Swan.emu/src/main/EmuMenuViews.cc | 19 +- imagine/include/imagine/base/Screen.hh | 2 + imagine/include/imagine/base/Window.hh | 5 +- .../base/android/AndroidApplication.hh | 2 +- .../imagine/base/android/Choreographer.hh | 4 +- imagine/include/imagine/base/baseDefs.hh | 1 + .../imagine/base/iphone/IOSApplication.hh | 4 +- .../include/imagine/base/iphone/IOSScreen.hh | 11 + .../include/imagine/base/x11/XApplication.hh | 3 +- imagine/make/clang.mk | 3 + imagine/src/audio/coreaudio/coreaudio.cc | 2 + imagine/src/base/android/AndroidScreen.cc | 14 +- imagine/src/base/android/AndroidWindow.cc | 8 +- imagine/src/base/android/FrameTimer.cc | 33 +- imagine/src/base/common/Screen.cc | 6 + imagine/src/base/common/SimpleFrameTimer.cc | 1 + imagine/src/base/common/Window.cc | 29 +- .../src/base/common/eventloop/CFEventLoop.cc | 2 +- imagine/src/base/x11/FrameTimer.cc | 21 +- imagine/src/base/x11/XScreen.cc | 14 +- 66 files changed, 1201 insertions(+), 1047 deletions(-) create mode 100644 EmuFramework/include/emuframework/viewUtils.hh create mode 100644 EmuFramework/src/gui/FrameTimingView.cc create mode 100644 EmuFramework/src/gui/FrameTimingView.hh diff --git a/2600.emu/src/main/EmuMenuViews.cc b/2600.emu/src/main/EmuMenuViews.cc index 94e589fc5..4cf55adf0 100644 --- a/2600.emu/src/main/EmuMenuViews.cc +++ b/2600.emu/src/main/EmuMenuViews.cc @@ -21,6 +21,7 @@ #include #include #include +#include #undef Debugger #include "MainApp.hh" #include @@ -63,7 +64,7 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper(attachParams(), e, "Input 1 to 20", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 1 to 20", "", + [this](CollectTextInputView&, auto val) { if(system().optionPaddleDigitalSensitivity.isValid(val)) { @@ -269,7 +270,7 @@ class ConsoleOptionView : public TableView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) { switch(id) { - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); - case ViewID::VIDEO_OPTIONS: return std::make_unique(attach); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); + case ViewID::VIDEO_OPTIONS: return std::make_unique(attach, videoLayer); case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); default: return nullptr; } diff --git a/C64.emu/src/main/EmuMenuViews.cc b/C64.emu/src/main/EmuMenuViews.cc index 7e16034f1..f0a80d794 100644 --- a/C64.emu/src/main/EmuMenuViews.cc +++ b/C64.emu/src/main/EmuMenuViews.cc @@ -21,6 +21,7 @@ #include #include #include +#include #include "MainApp.hh" #include "VicePlugin.hh" #include @@ -174,9 +175,9 @@ class CustomVideoOptionView : public VideoOptionView, public MainAppHelper(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, max == 200 ? "Input 0 to 200" : "Input 0 to 400", "", - [=, this](EmuApp &app, auto val) + [=, this](CollectTextInputView&, auto val) { val *= 10; system().setColorSetting(setting, val); @@ -197,8 +198,7 @@ class CustomVideoOptionView : public VideoOptionView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) { case ViewID::MAIN_MENU: return std::make_unique(attach); case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); - case ViewID::VIDEO_OPTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::VIDEO_OPTIONS: return std::make_unique(attach, videoLayer); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); default: return nullptr; diff --git a/EmuFramework/build.mk b/EmuFramework/build.mk index 195935ca8..ba2a47589 100644 --- a/EmuFramework/build.mk +++ b/EmuFramework/build.mk @@ -35,6 +35,7 @@ gui/EmuView.cc \ gui/EmuViewController.cc \ gui/FilePathOptionView.cc \ gui/FilePicker.cc \ +gui/FrameTimingView.cc \ gui/GUIOptionView.cc \ gui/InputManagerView.cc \ gui/LoadProgressView.cc \ diff --git a/EmuFramework/include/emuframework/AppKeyCode.hh b/EmuFramework/include/emuframework/AppKeyCode.hh index d838b97d3..bc13c48b3 100644 --- a/EmuFramework/include/emuframework/AppKeyCode.hh +++ b/EmuFramework/include/emuframework/AppKeyCode.hh @@ -119,6 +119,6 @@ constexpr InputComponentDesc rewindUIComponents{"Rewind One State", rewindUIKeys std::string_view toString(AppKeyCode); -constexpr const char *playerNumStrings[] {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}; +constexpr std::array playerNumStrings{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}; } diff --git a/EmuFramework/include/emuframework/AudioOptionView.hh b/EmuFramework/include/emuframework/AudioOptionView.hh index d2d7f0b20..da7f7f5a1 100644 --- a/EmuFramework/include/emuframework/AudioOptionView.hh +++ b/EmuFramework/include/emuframework/AudioOptionView.hh @@ -30,10 +30,11 @@ class EmuAudio; class AudioOptionView : public TableView, public EmuAppHelper { public: - AudioOptionView(ViewAttachParams attach, bool customMenu = false); + AudioOptionView(ViewAttachParams attach, EmuAudio&, bool customMenu = false); void loadStockItems(); protected: + EmuAudio &audio; BoolMenuItem snd; BoolMenuItem soundDuringFastSlowMode; TextMenuItem soundVolumeItem[4]; diff --git a/EmuFramework/include/emuframework/Cheats.hh b/EmuFramework/include/emuframework/Cheats.hh index 37426f4b1..8f0be2a41 100644 --- a/EmuFramework/include/emuframework/Cheats.hh +++ b/EmuFramework/include/emuframework/Cheats.hh @@ -16,6 +16,7 @@ along with EmuFramework. If not, see */ #include +#include #include #include #include @@ -73,9 +74,9 @@ public: IG_forward(cheatName), attach, [this](const Input::Event &e) { - app().template pushAndShowNewCollectValueInputView(attachParams(), e, + pushAndShowNewCollectValueInputView(attachParams(), e, "Input description", static_cast(this)->cheatNameString(), - [this](EmuApp &, auto str) + [this](CollectTextInputView&, auto str) { name.compile(str); static_cast(this)->renamed(str); diff --git a/EmuFramework/include/emuframework/EmuApp.hh b/EmuFramework/include/emuframework/EmuApp.hh index a7f736766..85af35d2e 100644 --- a/EmuFramework/include/emuframework/EmuApp.hh +++ b/EmuFramework/include/emuframework/EmuApp.hh @@ -29,8 +29,6 @@ #include #include #include -#include -#include #include #include #include @@ -107,11 +105,6 @@ struct AssetDesc constexpr auto filename() const { return assetFilename[fileIdx()]; } }; -enum class ScanValueMode -{ - NORMAL, ALLOW_BLANK -}; - WISE_ENUM_CLASS((ImageChannel, uint8_t), All, Red, @@ -182,11 +175,6 @@ public: void reloadSystem(EmuSystemCreateParams params = {}); void onSystemCreated(); void promptSystemReloadDueToSetOption(ViewAttachParams, const Input::Event &, EmuSystemCreateParams params = {}); - void pushAndShowNewCollectTextInputView(ViewAttachParams, const Input::Event &, - const char *msgText, const char *initialContent, CollectTextInputView::OnTextDelegate); - void pushAndShowNewYesNoAlertView(ViewAttachParams, const Input::Event &, - const char *label, const char *choice1, const char *choice2, - TextMenuItem::SelectDelegate onYes, TextMenuItem::SelectDelegate onNo); void pushAndShowModalView(std::unique_ptr v, const Input::Event &); void pushAndShowModalView(std::unique_ptr v); void popModalViews(); @@ -219,7 +207,7 @@ public: FS::PathString validSearchPath(const FS::PathString &) const; static void updateLegacySavePath(IG::ApplicationContext, CStringView path); auto screenshotDirectory() const { return system().userPath(userScreenshotPath); } - static std::unique_ptr makeCustomView(ViewAttachParams attach, ViewID id); + std::unique_ptr makeCustomView(ViewAttachParams attach, ViewID id); bool handleKeyInput(KeyInfo, const Input::Event &srcEvent); bool handleAppActionKeyInput(InputAction, const Input::Event &srcEvent); void handleSystemKeyInput(KeyInfo, Input::Action, uint32_t metaState = 0, SystemKeyInputFlags flags = {}); @@ -244,7 +232,7 @@ public: Gfx::TextureSpan asset(AssetID) const; Gfx::TextureSpan asset(AssetDesc) const; VController &defaultVController() { return inputManager.vController; } - static std::unique_ptr makeView(ViewAttachParams, ViewID); + std::unique_ptr makeView(ViewAttachParams, ViewID); std::unique_ptr makeCloseContentView(); void applyOSNavStyle(IG::ApplicationContext, bool inEmu); void setCPUNeedsLowLatency(IG::ApplicationContext, bool needed); @@ -290,8 +278,8 @@ public: float videoAspectRatio() const; float defaultVideoAspectRatio() const; IG::PixelFormat videoEffectPixelFormat() const; - bool setVideoZoom(uint8_t val); - bool setViewportZoom(uint8_t val); + bool setContentScale(uint8_t val); + bool setMenuScale(int8_t val); bool supportsShowOnSecondScreen(ApplicationContext ctx) { return ctx.androidSDK() >= 17; } void setContentRotation(IG::Rotation); void updateVideoContentRotation(); @@ -361,132 +349,11 @@ public: postMessage(secs, true, IG_forward(msg)); } - template T> - static std::pair scanValue(const char *str, ScanValueMode mode) - { - return {str, mode == ScanValueMode::ALLOW_BLANK || strlen(str) ? 1 : 0}; - } - - template - static std::pair scanValue(const char *str, ScanValueMode) - { - int val; - int items = sscanf(str, "%d", &val); - return {val, items}; - } - - template - static std::pair scanValue(const char *str, ScanValueMode) - { - T val, denom; - int items = sscanf(str, std::is_same_v ? "%lf /%lf" : "%f /%f", &val, &denom); - if(items > 1 && denom != 0) - { - val /= denom; - } - return {val, items}; - } - - template - requires std::same_as> || std::same_as> - static std::pair scanValue(const char *str, ScanValueMode) - { - // special case for getting a fraction - using PairValue = typename T::first_type; - PairValue val, denom{}; - int items = sscanf(str, std::is_same_v ? "%lf /%lf" : "%f /%f", &val, &denom); - if(denom == 0) - { - denom = 1.; - } - return {{val, denom}, items}; - } - - template - requires std::same_as> - static std::pair scanValue(const char *str, ScanValueMode) - { - using PairValue = typename T::first_type; - PairValue val, val2{}; - int items = sscanf(str, "%d %d", &val, &val2); - return {{val, val2}, items}; - } - - template - void pushAndShowNewCollectValueInputView(ViewAttachParams attach, const Input::Event &e, - CStringView msgText, CStringView initialContent, IG::Callable auto &&collectedValueFunc) - { - pushAndShowNewCollectTextInputView(attach, e, msgText, initialContent, - [collectedValueFunc](CollectTextInputView &view, const char *str) - { - if(!str) - { - view.dismiss(); - return false; - } - auto &app = get(view.appContext()); - auto [val, items] = scanValue(str, mode); - if(items <= 0) - { - app.postErrorMessage("Enter a value"); - return true; - } - else if(!collectedValueFunc(app, val)) - { - return true; - } - else - { - view.dismiss(); - return false; - } - }); - } - - template - void pushAndShowNewCollectValueRangeInputView(ViewAttachParams attach, const Input::Event &e, - CStringView msgText, CStringView initialContent, IG::Callable auto &&collectedValueFunc) - { - pushAndShowNewCollectValueInputView(attach, e, msgText, initialContent, - [collectedValueFunc](EmuApp &app, auto val) - { - if(val >= low && val <= high) - { - return collectedValueFunc(app, val); - } - else - { - app.postErrorMessage("Value not in range"); - return false; - } - }); - } - - template - void pushAndShowNewCollectValuePairRangeInputView(ViewAttachParams attach, const Input::Event &e, - CStringView msgText, CStringView initialContent, Callable> auto &&collectedValueFunc) - { - pushAndShowNewCollectValueInputView>(attach, e, msgText, initialContent, - [collectedValueFunc](EmuApp &app, auto val) - { - if(val.first >= low && val.first <= high && val.second >= low2 && val.second <= high2) - { - return collectedValueFunc(app, val); - } - else - { - app.postErrorMessage("Values not in range"); - return false; - } - }); - } - protected: IG::FontManager fontManager; mutable Gfx::Renderer renderer; ViewManager viewManager; public: - IG::Audio::Manager audioManager; EmuAudio audio; EmuVideo video; EmuVideoLayer videoLayer; @@ -542,8 +409,8 @@ public: PropertyDesc{.defaultValue = true, .mutableDefault = true}> showsBluetoothScan; Property{.defaultValue = PIXEL_NONE, .isValid = imageEffectPixelFormatIsValid}> imageEffectPixelFormat; - Property{.defaultValue = 100, .isValid = optionViewportZoomIsValid}> viewportZoom; - Property{.defaultValue = 100, .isValid = optionImageZoomIsValid}> imageZoom; + Property{.defaultValue = 100, .isValid = optionMenuScaleIsValid}> menuScale; + Property{.defaultValue = 100, .isValid = optionContentScaleIsValid}> contentScale; ConditionalProperty showOnSecondScreen; Property textureBufferMode; Property */ #include +#include #include #include #include @@ -55,7 +56,7 @@ public: MULTI_UNDERRUN }; - EmuAudio(const IG::Audio::Manager &audioManager); + EmuAudio(IG::ApplicationContext); void open(); void start(FloatSeconds bufferDuration); void stop(); @@ -81,9 +82,9 @@ public: void writeConfig(FileIO &) const; bool readConfig(MapIO &, unsigned key); + IG::Audio::Manager manager; protected: IG::Audio::OutputStream audioStream; - const IG::Audio::Manager &audioManager; RingBuffer rBuff; SteadyClockTimePoint lastUnderrunTime{}; double speedMultiplier{1.}; diff --git a/EmuFramework/include/emuframework/EmuOptions.hh b/EmuFramework/include/emuframework/EmuOptions.hh index d6e9ea290..1e214c045 100644 --- a/EmuFramework/include/emuframework/EmuOptions.hh +++ b/EmuFramework/include/emuframework/EmuOptions.hh @@ -37,7 +37,7 @@ enum { CFGKEY_SOUND = 0, CFGKEY_TOUCH_CONTROL_DISPLAY = 1, CFGKEY_TOUCH_CONTROL_CENTER_BTN_POS = 22, CFGKEY_TOUCH_CONTROL_TRIGGER_BTN_POS = 23, CFGKEY_TOUCH_CONTROL_MENU_POS = 24, CFGKEY_TOUCH_CONTROL_FF_POS = 25, CFGKEY_RECENT_CONTENT = 26, CFGKEY_GL_SYNC_HACK = 27, CFGKEY_PAUSE_UNFOCUSED = 28, - CFGKEY_IMAGE_ZOOM = 29, CFGKEY_TOUCH_CONTROL_IMG_PIXELS = 30, CFGKEY_SOUND_RATE = 31, + CFGKEY_CONTENT_SCALE = 29, CFGKEY_TOUCH_CONTROL_IMG_PIXELS = 30, CFGKEY_SOUND_RATE = 31, CFGKEY_NOTIFICATION_ICON = 32, CFGKEY_ICADE = 33, CFGKEY_TITLE_BAR = 34, CFGKEY_BACK_NAVIGATION = 35, CFGKEY_SYSTEM_ACTIONS_IS_DEFAULT_MENU = 36, CFGKEY_TOUCH_CONTROL_DIAGONAL_SENSITIVITY = 37, @@ -56,7 +56,7 @@ enum { CFGKEY_SOUND = 0, CFGKEY_TOUCH_CONTROL_DISPLAY = 1, CFGKEY_INPUT_KEY_CONFIGS = 60, CFGKEY_INPUT_DEVICE_CONFIGS = 61, CFGKEY_CONFIRM_OVERWRITE_STATE = 62, CFGKEY_NOTIFY_INPUT_DEVICE_CHANGE = 63, CFGKEY_AUDIO_SOLO_MIX = 64, CFGKEY_TOUCH_CONTROL_SHOW_ON_TOUCH = 65, - CFGKEY_TOUCH_CONTROL_SCALED_COORDINATES = 66, CFGKEY_VIEWPORT_ZOOM = 67, + CFGKEY_TOUCH_CONTROL_SCALED_COORDINATES = 66, CFGKEY_MENU_SCALE = 67, CFGKEY_VCONTROLLER_LAYOUT_POS = 68, CFGKEY_MOGA_INPUT_SYSTEM = 69, CFGKEY_FAST_MODE_SPEED = 70, CFGKEY_SHOW_BUNDLED_GAMES = 71, CFGKEY_IMAGE_EFFECT = 72, CFGKEY_SHOW_ON_2ND_SCREEN = 73, @@ -97,7 +97,7 @@ enum class InEmuTristate : uint8_t Off, InEmu, On }; -constexpr unsigned optionImageZoomIntegerOnly = 255, optionImageZoomIntegerOnlyY = 254; +constexpr unsigned optionContentScaleIntegerOnly = 255, optionContentScaleIntegerOnlyY = 254; constexpr const char *optionSavePathDefaultToken = ":DEFAULT:"; @@ -114,13 +114,13 @@ constexpr bool isValidFontSize(const auto &v) return v >= 2000 && v <= 10000; } -constexpr bool optionImageZoomIsValid(const auto &v) +constexpr bool optionContentScaleIsValid(const auto &v) { - return v == optionImageZoomIntegerOnly || v == optionImageZoomIntegerOnlyY + return v == optionContentScaleIntegerOnly || v == optionContentScaleIntegerOnlyY || (v >= 10 && v <= 200); } -constexpr bool optionViewportZoomIsValid(const auto &v) +constexpr bool optionMenuScaleIsValid(const auto &v) { return v >= 50 && v <= 100; } diff --git a/EmuFramework/include/emuframework/EmuVideoLayer.hh b/EmuFramework/include/emuframework/EmuVideoLayer.hh index c75b8c5d1..eb20e42a0 100644 --- a/EmuFramework/include/emuframework/EmuVideoLayer.hh +++ b/EmuFramework/include/emuframework/EmuVideoLayer.hh @@ -50,11 +50,8 @@ public: bool usingLinearFilter() const { return useLinearFilter; } void setBrightness(Gfx::Vec3); void onVideoFormatChanged(IG::PixelFormat effectFmt); - EmuVideo &emuVideo() const { return video; } Gfx::ColorSpace colorSpace() const { return colSpace; } bool srgbColorSpace() const { return colSpace == Gfx::ColorSpace::SRGB; } - void setZoom(uint8_t val) { zoom_ = val; } - auto zoom() const { return zoom_; } void setRotation(IG::Rotation); float evalAspectRatio(float aR); bool readConfig(MapIO &, unsigned key); @@ -65,10 +62,10 @@ public: return contentRect_; } + EmuVideo &video; private: VideoImageOverlay vidImgOverlay; IG::StaticArrayList effects; - EmuVideo &video; VideoImageEffect userEffect; Gfx::ITexQuads quad; Gfx::TextureSpan texture; @@ -84,7 +81,9 @@ private: ImageEffectId userEffectId{}; ImageOverlayId userOverlayEffectId{}; Gfx::ColorSpace colSpace{}; - uint8_t zoom_{100}; +public: + uint8_t scale{100}; +private: IG::Rotation rotation{}; bool useLinearFilter{true}; diff --git a/EmuFramework/include/emuframework/EmuViewController.hh b/EmuFramework/include/emuframework/EmuViewController.hh index 10fb661ef..bf53ceeec 100644 --- a/EmuFramework/include/emuframework/EmuViewController.hh +++ b/EmuFramework/include/emuframework/EmuViewController.hh @@ -62,7 +62,6 @@ class EmuViewController final: public ViewController, public EmuAppHelper, const Input::Event &, bool needsNavView, bool isModal = false) final; using ViewController::pushAndShow; void pushAndShowModal(std::unique_ptr, const Input::Event &, bool needsNavView); diff --git a/EmuFramework/include/emuframework/MainMenuView.hh b/EmuFramework/include/emuframework/MainMenuView.hh index 26e7f14ef..08a1d0307 100644 --- a/EmuFramework/include/emuframework/MainMenuView.hh +++ b/EmuFramework/include/emuframework/MainMenuView.hh @@ -36,15 +36,12 @@ public: void onShow() final; void loadFileBrowserItems(); void loadStandardItems(); - void setAudioVideo(EmuAudio &audio, EmuVideoLayer &videoLayer); virtual void reloadItems(); static constexpr int STANDARD_ITEMS = 15; static constexpr int MAX_SYSTEM_ITEMS = 5; protected: - EmuAudio *audio{}; - EmuVideoLayer *videoLayer{}; TextMenuItem loadGame; TextMenuItem systemActions; TextMenuItem recentGames; diff --git a/EmuFramework/include/emuframework/VideoOptionView.hh b/EmuFramework/include/emuframework/VideoOptionView.hh index df9224651..98d575534 100644 --- a/EmuFramework/include/emuframework/VideoOptionView.hh +++ b/EmuFramework/include/emuframework/VideoOptionView.hh @@ -29,35 +29,25 @@ class EmuVideoLayer; class EmuVideo; enum class ImageEffectId : uint8_t; enum class ImageChannel : uint8_t; -enum class VideoSystem: uint8_t; class VideoOptionView : public TableView, public EmuAppHelper { public: - VideoOptionView(ViewAttachParams attach, bool customMenu = false); + VideoOptionView(ViewAttachParams attach, EmuVideoLayer &videoLayer, bool customMenu = false); void place() final; void loadStockItems(); - void setEmuVideoLayer(EmuVideoLayer &videoLayer); protected: static constexpr int MAX_ASPECT_RATIO_ITEMS = 5; - EmuVideoLayer *videoLayer{}; - + EmuVideoLayer &videoLayer; StaticArrayList textureBufferModeItem; MultiChoiceMenuItem textureBufferMode; - TextMenuItem frameIntervalItem[5]; - MultiChoiceMenuItem frameInterval; - TextMenuItem frameRateItems[4]; - VideoSystem activeVideoSystem{}; - MultiChoiceMenuItem frameRate; - MultiChoiceMenuItem frameRatePAL; - ConditionalMember frameTimeStats; StaticArrayList aspectRatioItem; MultiChoiceMenuItem aspectRatio; - TextMenuItem zoomItem[6]; - MultiChoiceMenuItem zoom; - TextMenuItem viewportZoomItem[4]; - MultiChoiceMenuItem viewportZoom; + TextMenuItem contentScaleItems[6]; + MultiChoiceMenuItem contentScale; + TextMenuItem menuScaleItems[4]; + MultiChoiceMenuItem menuScale; TextMenuItem contentRotationItem[5]; MultiChoiceMenuItem contentRotation; TextMenuItem placeVideo; @@ -74,19 +64,8 @@ protected: MultiChoiceMenuItem windowPixelFormat; ConditionalMember secondDisplay; ConditionalMember showOnSecondScreen; - TextMenuItem imageBuffersItem[3]; - MultiChoiceMenuItem imageBuffers; - TextMenuItem frameClockItems[3]; - MultiChoiceMenuItem frameClock; - ConditionalMember presentModeItems[3]; - ConditionalMember presentMode; TextMenuItem renderPixelFormatItem[3]; MultiChoiceMenuItem renderPixelFormat; - ConditionalMember> screenFrameRateItems; - ConditionalMember screenFrameRate; - ConditionalMember presentationTimeItems[3]; - ConditionalMember presentationTime; - BoolMenuItem blankFrameInsertion; TextMenuItem brightnessItem[2]; TextMenuItem redItem[2]; TextMenuItem greenItem[2]; @@ -95,14 +74,11 @@ protected: MultiChoiceMenuItem red; MultiChoiceMenuItem green; MultiChoiceMenuItem blue; - TextHeadingMenuItem visualsHeading; - TextHeadingMenuItem screenShapeHeading; TextHeadingMenuItem colorLevelsHeading; TextHeadingMenuItem advancedHeading; TextHeadingMenuItem systemSpecificHeading; - StaticArrayList item; + StaticArrayList item; - bool onFrameTimeChange(VideoSystem vidSys, SteadyClockTime time); TextMenuItem::SelectDelegate setVideoBrightnessCustomDel(ImageChannel); void setAllColorLevelsSelected(MenuId); EmuVideo &emuVideo() const; diff --git a/EmuFramework/include/emuframework/viewUtils.hh b/EmuFramework/include/emuframework/viewUtils.hh new file mode 100644 index 000000000..e741aa35c --- /dev/null +++ b/EmuFramework/include/emuframework/viewUtils.hh @@ -0,0 +1,173 @@ +#pragma once + +/* This file is part of EmuFramework. + + Imagine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Imagine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EmuFramework. If not, see */ + +#include +#include +#include +#include + +namespace EmuEx +{ + +using namespace IG; + +enum class ScanValueMode +{ + Normal, AllowBlank +}; + +template T> +inline std::pair scanValue(const char* str, ScanValueMode mode) +{ + return {str, mode == ScanValueMode::AllowBlank || strlen(str) ? 1 : 0}; +} + +template +inline std::pair scanValue(const char* str, ScanValueMode) +{ + int val; + int items = sscanf(str, "%d", &val); + return {val, items}; +} + +template +inline std::pair scanValue(const char* str, ScanValueMode) +{ + T val, denom; + int items = sscanf(str, std::is_same_v ? "%lf /%lf" : "%f /%f", &val, &denom); + if(items > 1 && denom != 0) + { + val /= denom; + } + return {val, items}; +} + +template +requires std::same_as> || std::same_as> +inline std::pair scanValue(const char* str, ScanValueMode) +{ + // special case for getting a fraction + using PairValue = typename T::first_type; + PairValue val, denom{}; + int items = sscanf(str, std::is_same_v ? "%lf /%lf" : "%f /%f", &val, &denom); + if(denom == 0) + { + denom = 1.; + } + return {{val, denom}, items}; +} + +template +requires std::same_as> +inline std::pair scanValue(const char *str, ScanValueMode) +{ + using PairValue = typename T::first_type; + PairValue val, val2{}; + int items = sscanf(str, "%d %d", &val, &val2); + return {{val, val2}, items}; +} + +void pushAndShowModalView(std::unique_ptr v, const Input::Event& e); +Gfx::TextureSpan collectTextCloseAsset(ApplicationContext); +void postErrorMessage(ApplicationContext, std::string_view); + +inline void pushAndShowModalView(std::unique_ptr v) +{ + auto e = v->appContext().defaultInputEvent(); + pushAndShowModalView(std::move(v), e); +} + +inline void pushAndShowNewCollectTextInputView(ViewAttachParams attach, const Input::Event& e, const char* msgText, + const char *initialContent, CollectTextInputView::OnTextDelegate onText) +{ + pushAndShowModalView(std::make_unique(attach, msgText, initialContent, + collectTextCloseAsset(attach.appContext()), onText), e); +} + +template +inline void pushAndShowNewCollectValueInputView(ViewAttachParams attach, const Input::Event& e, + CStringView msgText, CStringView initialContent, IG::Callable auto&& collectedValueFunc) +{ + pushAndShowNewCollectTextInputView(attach, e, msgText, initialContent, + [collectedValueFunc](CollectTextInputView& view, const char* str) + { + if(!str) + { + view.dismiss(); + return false; + } + auto [val, items] = scanValue(str, mode); + if(items <= 0) + { + postErrorMessage(view.appContext(), "Enter a value"); + return true; + } + else if(!collectedValueFunc(view, val)) + { + return true; + } + else + { + view.dismiss(); + return false; + } + }); +} + +template +inline void pushAndShowNewCollectValueRangeInputView(ViewAttachParams attach, const Input::Event& e, + CStringView msgText, CStringView initialContent, IG::Callable auto&& collectedValueFunc) +{ + pushAndShowNewCollectValueInputView(attach, e, msgText, initialContent, + [collectedValueFunc](CollectTextInputView& view, auto val) + { + if(val >= low && val <= high) + { + return collectedValueFunc(view, val); + } + else + { + postErrorMessage(view.appContext(), "Value not in range"); + return false; + } + }); +} + +template +inline void pushAndShowNewCollectValuePairRangeInputView(ViewAttachParams attach, const Input::Event& e, + CStringView msgText, CStringView initialContent, Callable> auto&& collectedValueFunc) +{ + pushAndShowNewCollectValueInputView>(attach, e, msgText, initialContent, + [collectedValueFunc](CollectTextInputView& view, auto val) + { + if(val.first >= low && val.first <= high && val.second >= low2 && val.second <= high2) + { + return collectedValueFunc(view, val); + } + else + { + postErrorMessage(view.appContext(), "Values not in range"); + return false; + } + }); +} + +inline void pushAndShowNewYesNoAlertView(ViewAttachParams, const Input::Event&, + const char* label, const char* choice1, const char* choice2, + TextMenuItem::SelectDelegate onYes, TextMenuItem::SelectDelegate onNo); + +} diff --git a/EmuFramework/src/ConfigFile.cc b/EmuFramework/src/ConfigFile.cc index 6eb1beb50..db464b522 100644 --- a/EmuFramework/src/ConfigFile.cc +++ b/EmuFramework/src/ConfigFile.cc @@ -59,8 +59,8 @@ void EmuApp::saveConfigFile(FileIO &io) writeConfigHeader(io); recentContent.writeConfig(io); writeOptionValueIfNotDefault(io, imageEffectPixelFormat); - writeOptionValueIfNotDefault(io, viewportZoom); - writeOptionValueIfNotDefault(io, imageZoom); + writeOptionValueIfNotDefault(io, menuScale); + writeOptionValueIfNotDefault(io, contentScale); writeOptionValueIfNotDefault(io, fontSize); writeOptionValueIfNotDefault(io, showsBluetoothScan); writeOptionValueIfNotDefault(io, showsNotificationIcon); @@ -78,7 +78,7 @@ void EmuApp::saveConfigFile(FileIO &io) writeOptionValueIfNotDefault(io, menuOrientation); writeOptionValue(io, CFGKEY_BACK_NAVIGATION, viewManager.needsBackControlOption()); writeOptionValue(io, CFGKEY_SWAPPED_GAMEPAD_CONFIM, swappedConfirmKeysOption()); - writeOptionValue(io, CFGKEY_AUDIO_SOLO_MIX, audioManager.soloMixOption()); + writeOptionValue(io, CFGKEY_AUDIO_SOLO_MIX, audio.manager.soloMixOption()); writeOptionValue(io, CFGKEY_WINDOW_PIXEL_FORMAT, windowDrawablePixelFormatOption()); writeOptionValue(io, CFGKEY_VIDEO_COLOR_SPACE, windowDrawableColorSpaceOption()); writeOptionValue(io, CFGKEY_RENDER_PIXEL_FORMAT, renderPixelFormatOption()); @@ -196,8 +196,8 @@ EmuApp::ConfigParams EmuApp::loadConfigFile(IG::ApplicationContext ctx) case CFGKEY_FONT_Y_SIZE: return readOptionValue(io, fontSize); case CFGKEY_GAME_ORIENTATION: return readOptionValue(io, emuOrientation); case CFGKEY_MENU_ORIENTATION: return readOptionValue(io, menuOrientation); - case CFGKEY_IMAGE_ZOOM: return readOptionValue(io, imageZoom); - case CFGKEY_VIEWPORT_ZOOM: return readOptionValue(io, viewportZoom); + case CFGKEY_CONTENT_SCALE: return readOptionValue(io, contentScale); + case CFGKEY_MENU_SCALE: return readOptionValue(io, menuScale); case CFGKEY_SHOW_ON_2ND_SCREEN: return readOptionValue(io, showOnSecondScreen); case CFGKEY_IMAGE_EFFECT_PIXEL_FORMAT: return readOptionValue(io, imageEffectPixelFormat); case CFGKEY_RENDER_PIXEL_FORMAT: @@ -241,7 +241,7 @@ EmuApp::ConfigParams EmuApp::loadConfigFile(IG::ApplicationContext ctx) return used(presentationTimeMode) ? readOptionValue(io, presentationTimeMode, [](auto m){return m <= lastEnum;}) : false; case CFGKEY_FRAME_CLOCK: return readOptionValue(io, frameTimeSource); case CFGKEY_AUDIO_SOLO_MIX: - audioManager.setSoloMix(readOptionValue(io)); + audio.manager.setSoloMix(readOptionValue(io)); return true; case CFGKEY_SAVE_PATH: return readStringOptionValue(io, [&](auto &&path){system().setUserSaveDirectory(path);}); diff --git a/EmuFramework/src/EmuApp.cc b/EmuFramework/src/EmuApp.cc index 99bb11b1c..40f8e9812 100644 --- a/EmuFramework/src/EmuApp.cc +++ b/EmuFramework/src/EmuApp.cc @@ -93,8 +93,7 @@ EmuApp::EmuApp(ApplicationInitParams initParams, ApplicationContext &ctx): Application{initParams}, fontManager{ctx}, renderer{ctx}, - audioManager{ctx}, - audio{audioManager}, + audio{ctx}, videoLayer{video, defaultVideoAspectRatio()}, inputManager{ctx}, vibrationManager{ctx}, @@ -411,7 +410,7 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio if(auto launchGame = parseCommandArgs(initParams.commandArgs()); launchGame) system().setInitialLoadPath(launchGame); - audioManager.setMusicVolumeControlHint(); + audio.manager.setMusicVolumeControlHint(); if(!renderer.supportsColorSpace()) windowDrawableConf.colorSpace = {}; applyOSNavStyle(ctx, false); @@ -419,7 +418,7 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio ctx.addOnResume( [this](IG::ApplicationContext ctx, bool focused) { - audioManager.startSession(); + audio.manager.startSession(); audio.open(); return true; }); @@ -437,7 +436,7 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio } } audio.close(); - audioManager.endSession(); + audio.manager.endSession(); saveConfigFile(ctx); saveSystemOptions(); #ifdef CONFIG_INPUT_BLUETOOTH @@ -479,7 +478,7 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio } auto &screen = *win.screen(); winData.viewController.placeElements(); - winData.viewController.pushAndShowMainMenu(viewAttach, videoLayer, audio); + winData.viewController.pushAndShow(makeView(viewAttach, ViewID::MAIN_MENU)); configureSecondaryScreens(); video.setOnFormatChanged( [this, &viewController = winData.viewController](EmuVideo &) @@ -492,7 +491,7 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio videoLayer.setRendererTask(renderer.task()); applyRenderPixelFormat(); videoLayer.updateEffect(system(), videoEffectPixelFormat()); - videoLayer.setZoom(imageZoom); + videoLayer.scale = contentScale; system().onFrameUpdate = [this](FrameParams params) { emuSystemTask.updateFrameParams(params); @@ -732,15 +731,7 @@ bool EmuApp::advanceFrames(FrameParams frameParams, EmuSystemTask *taskPtr) IG::Viewport EmuApp::makeViewport(const IG::Window &win) const { - IG::WindowRect viewRect = layoutBehindSystemUI ? win.bounds() : win.contentBounds(); - if(viewportZoom != 100) - { - WPt viewCenter{viewRect.xSize() / 2, viewRect.ySize() / 2}; - viewRect -= viewCenter; - viewRect *= viewportZoom / 100.f; - viewRect += viewCenter; - } - return win.viewport(viewRect); + return win.viewport(layoutBehindSystemUI ? win.bounds() : win.contentBounds()); } void WindowData::updateWindowViewport(const IG::Window &win, IG::Viewport viewport, const IG::Gfx::Renderer &r) @@ -873,7 +864,6 @@ void EmuApp::startEmulation() emuSystemTask.start(); setCPUNeedsLowLatency(appContext(), true); system().start(*this); - log.info("timestamp source:{}", wise_enum::to_string(effectiveFrameTimeSource())); addOnFrameDelayed(); } @@ -895,7 +885,7 @@ void EmuApp::pauseEmulation() system().pause(*this); setRunSpeed(1.); videoLayer.setBrightness(videoBrightnessRGB * pausedVideoBrightnessScale); - viewController().emuWindow().setDrawEventPriority(); + emuWindow().setDrawEventPriority(); removeOnFrame(); } @@ -904,19 +894,6 @@ bool EmuApp::hasArchiveExtension(std::string_view name) return FS::hasArchiveExtension(name); } -void EmuApp::pushAndShowNewCollectTextInputView(ViewAttachParams attach, const Input::Event &e, const char *msgText, - const char *initialContent, CollectTextInputView::OnTextDelegate onText) -{ - pushAndShowModalView(std::make_unique(attach, msgText, initialContent, - collectTextCloseAsset(), onText), e); -} - -void EmuApp::pushAndShowNewYesNoAlertView(ViewAttachParams attach, const Input::Event &e, const char *label, - const char *choice1, const char *choice2, TextMenuItem::SelectDelegate onYes, TextMenuItem::SelectDelegate onNo) -{ - pushAndShowModalView(std::make_unique(attach, label, choice1, choice2, YesNoAlertView::Delegates{onYes, onNo}), e); -} - void EmuApp::pushAndShowModalView(std::unique_ptr v, const Input::Event &e) { viewController().pushAndShowModal(std::move(v), e, false); @@ -1949,8 +1926,8 @@ std::unique_ptr EmuApp::makeView(ViewAttachParams attach, ViewID id) { case ViewID::MAIN_MENU: return std::make_unique(attach); case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); - case ViewID::VIDEO_OPTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::VIDEO_OPTIONS: return std::make_unique(attach, videoLayer); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); case ViewID::GUI_OPTIONS: return std::make_unique(attach); @@ -1995,4 +1972,25 @@ EmuApp &gApp() { return *gAppPtr; } IG::ApplicationContext gAppContext() { return gApp().appContext(); } +void pushAndShowModalView(std::unique_ptr v, const Input::Event &e) +{ + v->appContext().applicationAs().viewController().pushAndShowModal(std::move(v), e, false); +} + +void pushAndShowNewYesNoAlertView(ViewAttachParams attach, const Input::Event &e, const char *label, + const char *choice1, const char *choice2, TextMenuItem::SelectDelegate onYes, TextMenuItem::SelectDelegate onNo) +{ + attach.appContext().applicationAs().pushAndShowModalView(std::make_unique(attach, label, choice1, choice2, YesNoAlertView::Delegates{onYes, onNo}), e); +} + +Gfx::TextureSpan collectTextCloseAsset(ApplicationContext ctx) +{ + return ctx.applicationAs().collectTextCloseAsset(); +} + +void postErrorMessage(ApplicationContext ctx, std::string_view s) +{ + ctx.applicationAs().postErrorMessage(s); +} + } diff --git a/EmuFramework/src/EmuAudio.cc b/EmuFramework/src/EmuAudio.cc index 4458cab8a..4c4a70451 100644 --- a/EmuFramework/src/EmuAudio.cc +++ b/EmuFramework/src/EmuAudio.cc @@ -71,9 +71,9 @@ static void stopAudioStats() #endif } -EmuAudio::EmuAudio(const IG::Audio::Manager &audioManager): - audioManager{audioManager}, - defaultRate{EmuSystem::forcedSoundRate ? EmuSystem::forcedSoundRate : audioManager.nativeRate()}, +EmuAudio::EmuAudio(ApplicationContext ctx): + manager{ctx}, + defaultRate{EmuSystem::forcedSoundRate ? EmuSystem::forcedSoundRate : manager.nativeRate()}, rate_{defaultRate} {} size_t EmuAudio::framesFree() const @@ -153,7 +153,7 @@ void EmuAudio::open() { close(); if(isEnabled()) - audioStream.setApi(audioManager, outputAPI()); + audioStream.setApi(manager, outputAPI()); } void EmuAudio::start(FloatSeconds bufferDuration) @@ -172,7 +172,7 @@ void EmuAudio::start(FloatSeconds bufferDuration) { resizeAudioBuffer(targetBufferFillBytes); audioWriteState = AudioWriteState::BUFFER; - IG::Audio::Format outputFormat{inputFormat.rate, audioManager.nativeSampleFormat(), inputFormat.channels}; + IG::Audio::Format outputFormat{inputFormat.rate, manager.nativeSampleFormat(), inputFormat.channels}; IG::Audio::OutputStreamConfig outputConf { outputFormat, diff --git a/EmuFramework/src/EmuOptions.cc b/EmuFramework/src/EmuOptions.cc index f6e08d751..efdd4e5c0 100644 --- a/EmuFramework/src/EmuOptions.cc +++ b/EmuFramework/src/EmuOptions.cc @@ -85,24 +85,23 @@ IG::PixelFormat EmuApp::videoEffectPixelFormat() const return windowPixelFormat(); } -bool EmuApp::setVideoZoom(uint8_t val) +bool EmuApp::setContentScale(uint8_t val) { - if(!imageZoom.set(val)) + if(!contentScale.set(val)) return false; - log.info("set video zoom:{}", imageZoom.value()); - videoLayer.setZoom(val); + log.info("set content scale:{}", contentScale.value()); + videoLayer.scale = val; viewController().placeEmuViews(); viewController().postDrawToEmuWindows(); return true; } -bool EmuApp::setViewportZoom(uint8_t val) +bool EmuApp::setMenuScale(int8_t val) { - if(!viewportZoom.set(val)) + if(!menuScale.set(val)) return false; - log.info("set viewport zoom:{}", viewportZoom.value()); - auto &win = appContext().mainWindow(); - viewController().updateMainWindowViewport(win, makeViewport(win), renderer.task()); + log.info("set menu scale:{}", menuScale.value()); + viewController().placeElements(); viewController().postDrawToEmuWindows(); return true; } diff --git a/EmuFramework/src/EmuVideoLayer.cc b/EmuFramework/src/EmuVideoLayer.cc index 5d0ae2c3b..8795aacf2 100644 --- a/EmuFramework/src/EmuVideoLayer.cc +++ b/EmuFramework/src/EmuVideoLayer.cc @@ -46,12 +46,11 @@ void EmuVideoLayer::place(IG::WindowRect viewRect, IG::WindowRect displayRect, E if(sys.hasContent() && video.size().x) { auto viewportAspectRatio = displayRect.xSize() / (float)displayRect.ySize(); - auto zoom = zoom_; auto contentSize = video.size(); if(isSideways(rotation)) std::swap(contentSize.x, contentSize.y); contentRect_ = {}; - if(zoom == optionImageZoomIntegerOnly || zoom == optionImageZoomIntegerOnlyY) + if(scale == optionContentScaleIntegerOnly || scale == optionContentScaleIntegerOnlyY) { int x = contentSize.x, y = contentSize.y; @@ -99,13 +98,13 @@ void EmuVideoLayer::place(IG::WindowRect viewRect, IG::WindowRect displayRect, E contentRect_.x2 = x * scaleFactor; contentRect_.y2 = y * scaleFactor; } - if(zoom <= 200 || zoom == optionImageZoomIntegerOnlyY) + if(scale <= 200 || scale == optionContentScaleIntegerOnlyY) { auto aR = evalAspectRatio(viewportAspectRatio < 1.f ? portraitAspectRatio : landscapeAspectRatio) * sys.videoAspectRatioScale(); if(isSideways(rotation)) aR = 1. / aR; - if(zoom == optionImageZoomIntegerOnlyY) + if(scale == optionContentScaleIntegerOnlyY) { // get width from previously calculated pixel height float width = contentRect_.ySize() * (float)aR; @@ -124,9 +123,9 @@ void EmuVideoLayer::place(IG::WindowRect viewRect, IG::WindowRect displayRect, E } contentRect_.x2 = size.x; contentRect_.y2 = size.y; - if(zoom != 100) + if(scale != 100) { - auto scaler = zoom / 100.f; + auto scaler = scale / 100.f; contentRect_.x2 *= scaler; contentRect_.y2 *= scaler; } diff --git a/EmuFramework/src/gui/AudioOptionView.cc b/EmuFramework/src/gui/AudioOptionView.cc index 1330d07e7..ca33acae3 100644 --- a/EmuFramework/src/gui/AudioOptionView.cc +++ b/EmuFramework/src/gui/AudioOptionView.cc @@ -14,30 +14,33 @@ along with EmuFramework. If not, see */ #include -#include +#include +#include +#include #include namespace EmuEx { -AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): +AudioOptionView::AudioOptionView(ViewAttachParams attach, EmuAudio& audio_, bool customMenu): TableView{"Audio Options", attach, item}, + audio{audio_}, snd { "Sound", attach, - app().audio.isEnabled(), + audio_.isEnabled(), [this](BoolMenuItem &item) { - app().audio.setEnabled(item.flipBoolValue(*this)); + audio.setEnabled(item.flipBoolValue(*this)); } }, soundDuringFastSlowMode { "Sound During Fast/Slow Mode", attach, - app().audio.isEnabledDuringAltSpeed(), + audio_.isEnabledDuringAltSpeed(), [this](BoolMenuItem &item) { - app().audio.setEnabledDuringAltSpeed(item.flipBoolValue(*this)); + audio.setEnabledDuringAltSpeed(item.flipBoolValue(*this)); } }, soundVolumeItem @@ -45,13 +48,12 @@ AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): {"100%", attach, {.id = 100}}, {"50%", attach, {.id = 50}}, {"25%", attach, {.id = 25}}, - {"Custom Value", attach, - [this](const Input::Event &e) + {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 125", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 125", "", + [this](CollectTextInputView &, auto val) { - app.audio.setMaxVolume(val); + audio.setMaxVolume(val); soundVolume.setSelected(MenuId{val}, *this); dismissPrevious(); return true; @@ -63,15 +65,15 @@ AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): soundVolume { "Volume", attach, - MenuId{app().audio.maxVolume()}, + MenuId{audio_.maxVolume()}, soundVolumeItem, { .onSetDisplayString = [this](auto idx, Gfx::Text &t) { - t.resetString(std::format("{}%", app().audio.maxVolume())); + t.resetString(std::format("{}%", audio.maxVolume())); return true; }, - .defaultItemOnSelect = [this](TextMenuItem &item) { app().audio.setMaxVolume(item.id); } + .defaultItemOnSelect = [this](TextMenuItem &item) { audio.setMaxVolume(item.id); } }, }, soundBuffersItem @@ -87,19 +89,19 @@ AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): soundBuffers { "Buffer Size In Frames", attach, - MenuId{app().audio.soundBuffers}, + MenuId{audio_.soundBuffers}, soundBuffersItem, { - .defaultItemOnSelect = [this](TextMenuItem &item) { app().audio.soundBuffers = item.id; } + .defaultItemOnSelect = [this](TextMenuItem &item) { audio.soundBuffers = item.id; } }, }, addSoundBuffersOnUnderrun { "Auto-increase Buffer Size", attach, - app().audio.addSoundBuffersOnUnderrunSetting, + audio_.addSoundBuffersOnUnderrunSetting, [this](BoolMenuItem &item) { - app().audio.addSoundBuffersOnUnderrunSetting = item.flipBoolValue(*this); + audio.addSoundBuffersOnUnderrunSetting = item.flipBoolValue(*this); } }, audioRateItem @@ -109,16 +111,16 @@ AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): decltype(audioRateItem) items; items.emplace_back("Device Native", attach, [this](View &view) { - app().audio.setRate(0); - audioRate.setSelected(MenuId{app().audio.rate()}); + audio.setRate(0); + audioRate.setSelected(MenuId{audio.rate()}, view); view.dismiss(); return false; }); - auto setRateDel = [this](TextMenuItem &item) { app().audio.setRate(item.id); }; + auto setRateDel = [this](TextMenuItem &item) { audio.setRate(item.id); }; items.emplace_back("22KHz", attach, setRateDel, MenuItem::Config{.id = 22050}); items.emplace_back("32KHz", attach, setRateDel, MenuItem::Config{.id = 32000}); items.emplace_back("44KHz", attach, setRateDel, MenuItem::Config{.id = 44100}); - if(app().audio.maxRate() >= 48000) + if(audio.maxRate() >= 48000) items.emplace_back("48KHz", attach, setRateDel, MenuItem::Config{.id = 48000}); return items; }() @@ -126,16 +128,16 @@ AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): audioRate { "Sound Rate", attach, - MenuId{app().audio.rate()}, + MenuId{audio_.rate()}, audioRateItem }, audioSoloMix { "Mix With Other Apps", attach, - !app().audioManager.soloMix(), + !audio_.manager.soloMix(), [this](BoolMenuItem &item) { - app().audioManager.setSoloMix(!item.flipBoolValue(*this)); + audio.manager.setSoloMix(!item.flipBoolValue(*this)); } }, apiItem @@ -145,17 +147,16 @@ AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): ApiItemContainer items{}; items.emplace_back("Auto", attachParams(), [this](View &view) { - app().audio.setOutputAPI(Audio::Api::DEFAULT); - doIfUsed(api, [&](auto &api){ api.setSelected(MenuId{app().audioManager.makeValidAPI()}); }); + audio.setOutputAPI(Audio::Api::DEFAULT); + doIfUsed(api, [&](auto &api){ api.setSelected(MenuId{audio.manager.makeValidAPI()}); }); view.dismiss(); return false; }); - auto &audioManager = app().audioManager; - for(auto desc: audioManager.audioAPIs()) + for(auto desc: audio.manager.audioAPIs()) { items.emplace_back(desc.name, attachParams(), [this](TextMenuItem &item) { - app().audio.setOutputAPI(Audio::Api(item.id.val)); + audio.setOutputAPI(Audio::Api(item.id.val)); }, MenuItem::Config{.id = desc.api}); } return items; @@ -164,7 +165,7 @@ AudioOptionView::AudioOptionView(ViewAttachParams attach, bool customMenu): api { "Audio Driver", attach, - MenuId{app().audioManager.makeValidAPI(app().audio.outputAPI())}, + MenuId{audio_.manager.makeValidAPI(audio_.outputAPI())}, apiItem } { diff --git a/EmuFramework/src/gui/AutosaveSlotView.cc b/EmuFramework/src/gui/AutosaveSlotView.cc index 25b35db86..96809baf4 100644 --- a/EmuFramework/src/gui/AutosaveSlotView.cc +++ b/EmuFramework/src/gui/AutosaveSlotView.cc @@ -16,6 +16,7 @@ #include "AutosaveSlotView.hh" #include #include +#include #include namespace EmuEx @@ -48,18 +49,18 @@ class EditAutosaveView : public TableView, public EmuAppHelper "Rename", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, + pushAndShowNewCollectValueInputView(attachParams(), e, "Input name", slotName, - [this](EmuApp &app, auto str) + [this](CollectTextInputView &, auto str) { - if(appContext().fileUriExists(app.system().contentLocalSaveDirectory(str))) + if(appContext().fileUriExists(system().contentLocalSaveDirectory(str))) { - app.postErrorMessage("A save slot with that name already exists"); + app().postErrorMessage("A save slot with that name already exists"); return false; } - if(!app.autosaveManager.renameSlot(slotName, str)) + if(!app().autosaveManager.renameSlot(slotName, str)) { - app.postErrorMessage("Error renaming save slot"); + app().postErrorMessage("Error renaming save slot"); return false; } srcView.updateItem(slotName, str); @@ -148,21 +149,21 @@ AutosaveSlotView::AutosaveSlotView(ViewAttachParams attach): { "Create New Save Slot", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, - "Save Slot Name", "", [this](EmuApp &app, auto str_) + pushAndShowNewCollectValueInputView(attachParams(), e, + "Save Slot Name", "", [this](CollectTextInputView &, auto str_) { std::string_view name{str_}; - if(appContext().fileUriExists(app.system().contentLocalSaveDirectory(name))) + if(appContext().fileUriExists(app().system().contentLocalSaveDirectory(name))) { - app.postErrorMessage("A save slot with that name already exists"); + app().postErrorMessage("A save slot with that name already exists"); return false; } - if(!app.autosaveManager.setSlot(name)) + if(!app().autosaveManager.setSlot(name)) { - app.postErrorMessage("Error creating save slot"); + app().postErrorMessage("Error creating save slot"); return false; } - app.showEmulation(); + app().showEmulation(); refreshItems(); return true; }); diff --git a/EmuFramework/src/gui/Cheats.cc b/EmuFramework/src/gui/Cheats.cc index a4f04becb..52d6ac239 100644 --- a/EmuFramework/src/gui/Cheats.cc +++ b/EmuFramework/src/gui/Cheats.cc @@ -42,7 +42,7 @@ BaseCheatsView::BaseCheatsView(ViewAttachParams attach): "Add/Edit", attach, [this](const Input::Event &e) { - auto editCheatsView = EmuApp::makeView(attachParams(), EmuApp::ViewID::EDIT_CHEATS); + auto editCheatsView = app().makeView(attachParams(), EmuApp::ViewID::EDIT_CHEATS); static_cast(editCheatsView.get())->setOnCheatListChanged( [this]() { diff --git a/EmuFramework/src/gui/EmuViewController.cc b/EmuFramework/src/gui/EmuViewController.cc index ea197cac2..c120917ec 100644 --- a/EmuFramework/src/gui/EmuViewController.cc +++ b/EmuFramework/src/gui/EmuViewController.cc @@ -78,14 +78,6 @@ EmuViewController::EmuViewController(ViewAttachParams viewAttach, emuView.setLayoutInputView(&inputView); } -void EmuViewController::pushAndShowMainMenu(ViewAttachParams viewAttach, EmuVideoLayer &videoLayer, - EmuAudio &emuAudio) -{ - auto mainMenu = EmuApp::makeView(viewAttach, EmuApp::ViewID::MAIN_MENU); - static_cast(mainMenu.get())->setAudioVideo(emuAudio, videoLayer); - viewStack.pushAndShow(std::move(mainMenu)); -} - static bool shouldExitFromViewRootWithoutPrompt(const Input::KeyEvent &e) { return e.map() == Input::Map::SYSTEM && (Config::envIsAndroid || Config::envIsLinux); @@ -231,6 +223,7 @@ void EmuViewController::showEmulationView(FrameTimeConfig frameTimeConfig) return; viewStack.top().onHide(); showingEmulation = true; + emuView.window().configureFrameTimeSource(app().frameTimeSource); configureWindowForEmulation(emuView.window(), frameTimeConfig, true); if(emuView.window() != inputView.window()) inputView.postDraw(); @@ -244,6 +237,7 @@ void EmuViewController::showMenuView(bool updateTopView) if(!showingEmulation) return; showingEmulation = false; + emuView.window().configureFrameTimeSource(FrameTimeSource::Unset); presentTime = {}; inputView.setSystemGestureExclusion(false); configureWindowForEmulation(emuView.window(), {}, false); @@ -272,7 +266,16 @@ void EmuViewController::placeElements() auto &winData = app().mainWindowData(); emuView.manager().setTableXIndentToDefault(appContext().mainWindow()); placeEmuViews(); - viewStack.place(winData.contentBounds(), winData.windowBounds()); + WRect contentBounds = winData.contentBounds(); + WRect windowBounds = winData.windowBounds(); + if(app().menuScale != 100) + { + float scaler = app().menuScale / 100.f; + contentBounds *= scaler; + contentBounds.setPos(winData.contentBounds().pos(C2DO), C2DO); + windowBounds = contentBounds; + } + viewStack.place(contentBounds, windowBounds); } void EmuViewController::updateMainWindowViewport(IG::Window &win, IG::Viewport viewport, Gfx::RendererTask &task) @@ -426,7 +429,7 @@ void EmuViewController::showSystemActionsView(ViewAttachParams attach, const Inp app().showUI(); if(!viewStack.contains("System Actions")) { - viewStack.pushAndShow(EmuApp::makeView(attach, EmuApp::ViewID::SYSTEM_ACTIONS), e); + viewStack.pushAndShow(app().makeView(attach, EmuApp::ViewID::SYSTEM_ACTIONS), e); } } diff --git a/EmuFramework/src/gui/FrameTimingView.cc b/EmuFramework/src/gui/FrameTimingView.cc new file mode 100644 index 000000000..1efd41c59 --- /dev/null +++ b/EmuFramework/src/gui/FrameTimingView.cc @@ -0,0 +1,445 @@ +/* This file is part of EmuFramework. + + Imagine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Imagine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EmuFramework. If not, see */ + +#include "FrameTimingView.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace EmuEx +{ + +constexpr SystemLogger log{"FrameTimingView"}; + +class DetectFrameRateView final: public View, public EmuAppHelper +{ +public: + using DetectFrameRateDelegate = DelegateFunc; + DetectFrameRateDelegate onDetectFrameTime; + IG::OnFrameDelegate detectFrameRate; + SteadyClockTime totalFrameTime{}; + SteadyClockTimePoint lastFrameTimestamp{}; + Gfx::Text fpsText; + int allTotalFrames{}; + int callbacks{}; + std::vector frameTimeSample{}; + bool useRenderTaskTime = false; + + DetectFrameRateView(ViewAttachParams attach): View(attach), + fpsText{attach.rendererTask, &defaultFace()} + { + defaultFace().precacheAlphaNum(attach.renderer()); + defaultFace().precache(attach.renderer(), "."); + fpsText.resetString("Preparing to detect frame rate..."); + useRenderTaskTime = !screen()->supportsTimestamps(); + frameTimeSample.reserve(std::round(screen()->frameRate() * 2.)); + } + + ~DetectFrameRateView() final + { + window().setIntendedFrameRate(0); + app().setCPUNeedsLowLatency(appContext(), false); + window().removeOnFrame(detectFrameRate); + } + + void place() final + { + fpsText.compile(); + } + + bool inputEvent(const Input::Event &e) final + { + if(e.keyEvent() && e.keyEvent()->pushed(Input::DefaultKey::CANCEL)) + { + log.info("aborted detection"); + dismiss(); + return true; + } + return false; + } + + void draw(Gfx::RendererCommands &__restrict__ cmds) final + { + using namespace IG::Gfx; + cmds.basicEffect().enableAlphaTexture(cmds); + fpsText.draw(cmds, viewRect().center(), C2DO, ColorName::WHITE); + } + + bool runFrameTimeDetection(SteadyClockTime timestampDiff, double slack) + { + const int framesToTime = frameTimeSample.capacity() * 10; + allTotalFrames++; + frameTimeSample.emplace_back(timestampDiff); + if(frameTimeSample.size() == frameTimeSample.capacity()) + { + bool stableFrameTime = true; + SteadyClockTime frameTimeTotal{}; + { + SteadyClockTime lastFrameTime{}; + for(auto frameTime : frameTimeSample) + { + frameTimeTotal += frameTime; + if(!stableFrameTime) + continue; + double frameTimeDiffSecs = + std::abs(IG::FloatSeconds(lastFrameTime - frameTime).count()); + if(lastFrameTime.count() && frameTimeDiffSecs > slack) + { + log.info("frame times differed by:{}", frameTimeDiffSecs); + stableFrameTime = false; + } + lastFrameTime = frameTime; + } + } + auto frameTimeTotalSecs = FloatSeconds(frameTimeTotal); + auto detectedFrameTimeSecs = frameTimeTotalSecs / (double)frameTimeSample.size(); + auto detectedFrameTime = round(detectedFrameTimeSecs); + { + if(detectedFrameTime.count()) + fpsText.resetString(std::format("{:g}fps", toHz(detectedFrameTimeSecs))); + else + fpsText.resetString("0fps"); + fpsText.compile(); + } + if(stableFrameTime) + { + log.info("found frame time:{}", detectedFrameTimeSecs); + onDetectFrameTime(detectedFrameTime); + dismiss(); + return false; + } + frameTimeSample.erase(frameTimeSample.cbegin()); + postDraw(); + } + else + { + //log.info("waiting for capacity:{}/{}", frameTimeSample.size(), frameTimeSample.capacity()); + } + if(allTotalFrames >= framesToTime) + { + onDetectFrameTime(SteadyClockTime{}); + dismiss(); + return false; + } + else + { + if(useRenderTaskTime) + postDraw(); + return true; + } + } + + void onAddedToController(ViewController *, const Input::Event &e) final + { + lastFrameTimestamp = SteadyClock::now(); + detectFrameRate = + [this](IG::FrameParams params) + { + const int callbacksToSkip = 10; + callbacks++; + if(callbacks < callbacksToSkip) + { + if(useRenderTaskTime) + postDraw(); + return true; + } + return runFrameTimeDetection(params.timestamp - std::exchange(lastFrameTimestamp, params.timestamp), 0.00175); + }; + window().addOnFrame(detectFrameRate); + app().setCPUNeedsLowLatency(appContext(), true); + } +}; + +static std::string makeFrameRateStr(VideoSystem vidSys, const OutputTimingManager &mgr) +{ + auto frameTimeOpt = mgr.frameTimeOption(vidSys); + if(frameTimeOpt == OutputTimingManager::autoOption) + return "Auto"; + else if(frameTimeOpt == OutputTimingManager::originalOption) + return "Original"; + else + return std::format("{:g}Hz", toHz(frameTimeOpt)); +} + +FrameTimingView::FrameTimingView(ViewAttachParams attach): + TableView{"Frame Timing Options", attach, item}, + frameIntervalItem + { + {"Full (No Skip)", attach, {.id = 0}}, + {"Full", attach, {.id = 1}}, + {"1/2", attach, {.id = 2}}, + {"1/3", attach, {.id = 3}}, + {"1/4", attach, {.id = 4}}, + }, + frameInterval + { + "Frame Rate Target", attach, + MenuId{app().frameInterval}, + frameIntervalItem, + MultiChoiceMenuItem::Config + { + .defaultItemOnSelect = [this](TextMenuItem &item) { app().frameInterval.setUnchecked(item.id); } + }, + }, + frameRateItems + { + {"Auto (Match screen when rates are similar)", attach, + [this] + { + if(!app().viewController().emuWindowScreen()->frameRateIsReliable()) + { + app().postErrorMessage("Reported rate potentially unreliable, " + "using the detected rate may give better results"); + } + onFrameTimeChange(activeVideoSystem, OutputTimingManager::autoOption); + }, {.id = OutputTimingManager::autoOption.count()} + }, + {"Original (Use emulated system's rate)", attach, + [this] + { + onFrameTimeChange(activeVideoSystem, OutputTimingManager::originalOption); + }, {.id = OutputTimingManager::originalOption.count()} + }, + {"Detect Custom Rate", attach, + [this](const Input::Event &e) + { + window().setIntendedFrameRate(system().frameRate()); + auto frView = makeView(); + frView->onDetectFrameTime = + [this](SteadyClockTime frameTime) + { + if(frameTime.count()) + { + if(onFrameTimeChange(activeVideoSystem, frameTime)) + dismissPrevious(); + } + else + { + app().postErrorMessage("Detected rate too unstable to use"); + } + }; + pushAndShowModal(std::move(frView), e); + return false; + } + }, + {"Custom Rate", attach, + [this](const Input::Event &e) + { + pushAndShowNewCollectValueInputView>(attachParams(), e, + "Input decimal or fraction", "", + [this](CollectTextInputView&, auto val) + { + if(onFrameTimeChange(activeVideoSystem, fromSeconds(val.second / val.first))) + { + if(activeVideoSystem == VideoSystem::NATIVE_NTSC) + frameRate.setSelected(defaultMenuId, *this); + else + frameRatePAL.setSelected(defaultMenuId, *this); + dismissPrevious(); + return true; + } + else + return false; + }); + return false; + }, {.id = defaultMenuId} + }, + }, + frameRate + { + "Frame Rate", attach, + app().outputTimingManager.frameTimeOptionAsMenuId(VideoSystem::NATIVE_NTSC), + frameRateItems, + { + .onSetDisplayString = [this](auto idx, Gfx::Text &t) + { + t.resetString(makeFrameRateStr(VideoSystem::NATIVE_NTSC, app().outputTimingManager)); + return true; + }, + .onSelect = [this](MultiChoiceMenuItem &item, View &view, const Input::Event &e) + { + activeVideoSystem = VideoSystem::NATIVE_NTSC; + item.defaultOnSelect(view, e); + }, + }, + }, + frameRatePAL + { + "Frame Rate (PAL)", attach, + app().outputTimingManager.frameTimeOptionAsMenuId(VideoSystem::PAL), + frameRateItems, + { + .onSetDisplayString = [this](auto idx, Gfx::Text &t) + { + t.resetString(makeFrameRateStr(VideoSystem::PAL, app().outputTimingManager)); + return true; + }, + .onSelect = [this](MultiChoiceMenuItem &item, View &view, const Input::Event &e) + { + activeVideoSystem = VideoSystem::PAL; + item.defaultOnSelect(view, e); + }, + }, + }, + frameTimeStats + { + "Show Frame Time Stats", attach, + app().showFrameTimeStats, + [this](BoolMenuItem &item) { app().showFrameTimeStats = item.flipBoolValue(*this); } + }, + frameClockItems + { + {"Auto", attach, MenuItem::Config{.id = FrameTimeSource::Unset}}, + {"Screen (Less latency & power use)", attach, MenuItem::Config{.id = FrameTimeSource::Screen}}, + {"Timer (Best for VRR displays)", attach, MenuItem::Config{.id = FrameTimeSource::Timer}}, + {"Renderer (May buffer multiple frames)", attach, MenuItem::Config{.id = FrameTimeSource::Renderer}}, + }, + frameClock + { + "Frame Clock", attach, + MenuId{FrameTimeSource(app().frameTimeSource)}, + frameClockItems, + MultiChoiceMenuItem::Config + { + .onSetDisplayString = [this](auto idx, Gfx::Text &t) + { + t.resetString(wise_enum::to_string(app().effectiveFrameTimeSource())); + return true; + }, + .defaultItemOnSelect = [this](TextMenuItem &item) + { + app().frameTimeSource = FrameTimeSource(item.id.val); + app().video.resetImage(); // texture can switch between single/double buffered + } + }, + }, + presentModeItems + { + {"Auto", attach, MenuItem::Config{.id = Gfx::PresentMode::Auto}}, + {"Immediate (Less compositor latency, may drop frames)", attach, MenuItem::Config{.id = Gfx::PresentMode::Immediate}}, + {"Queued (Better frame rate stability)", attach, MenuItem::Config{.id = Gfx::PresentMode::FIFO}}, + }, + presentMode + { + "Present Mode", attach, + MenuId{Gfx::PresentMode(app().presentMode)}, + presentModeItems, + MultiChoiceMenuItem::Config + { + .onSetDisplayString = [this](auto idx, Gfx::Text &t) + { + t.resetString(renderer().evalPresentMode(app().emuWindow(), app().presentMode) == Gfx::PresentMode::FIFO ? "Queued" : "Immediate"); + return true; + }, + .defaultItemOnSelect = [this](TextMenuItem &item) + { + app().presentMode = Gfx::PresentMode(item.id.val); + } + }, + }, + screenFrameRateItems + { + [&] + { + std::vector items; + auto setRateDel = [this](TextMenuItem &item) { app().overrideScreenFrameRate = std::bit_cast(item.id); }; + items.emplace_back("Off", attach, setRateDel, MenuItem::Config{.id = 0}); + for(auto rate : app().emuScreen().supportedFrameRates()) + items.emplace_back(std::format("{:g}Hz", rate), attach, setRateDel, MenuItem::Config{.id = std::bit_cast(rate)}); + return items; + }() + }, + screenFrameRate + { + "Override Screen Frame Rate", attach, + std::bit_cast(FrameRate(app().overrideScreenFrameRate)), + screenFrameRateItems + }, + presentationTimeItems + { + {"Full (Apply to all frame rate targets)", attach, MenuItem::Config{.id = PresentationTimeMode::full}}, + {"Basic (Only apply to lower frame rate targets)", attach, MenuItem::Config{.id = PresentationTimeMode::basic}}, + {"Off", attach, MenuItem::Config{.id = PresentationTimeMode::off}}, + }, + presentationTime + { + "Precise Frame Pacing", attach, + MenuId{PresentationTimeMode(app().presentationTimeMode)}, + presentationTimeItems, + MultiChoiceMenuItem::Config + { + .onSetDisplayString = [this](auto idx, Gfx::Text &t) + { + if(app().presentationTimeMode == PresentationTimeMode::off) + return false; + t.resetString(app().presentationTimeMode == PresentationTimeMode::full ? "Full" : "Basic"); + return true; + }, + .defaultItemOnSelect = [this](TextMenuItem &item) + { + app().presentationTimeMode = PresentationTimeMode(item.id.val); + } + }, + }, + blankFrameInsertion + { + "Allow Blank Frame Insertion", attach, + app().allowBlankFrameInsertion, + [this](BoolMenuItem &item) { app().allowBlankFrameInsertion = item.flipBoolValue(*this); } + }, + advancedHeading{"Advanced", attach} +{ + loadStockItems(); +} + +void FrameTimingView::loadStockItems() +{ + item.emplace_back(&frameInterval); + item.emplace_back(&frameRate); + if(EmuSystem::hasPALVideoSystem) + { + item.emplace_back(&frameRatePAL); + } + if(used(frameTimeStats)) + item.emplace_back(&frameTimeStats); + item.emplace_back(&advancedHeading); + item.emplace_back(&frameClock); + if(used(presentMode)) + item.emplace_back(&presentMode); + if(used(presentationTime) && renderer().supportsPresentationTime()) + item.emplace_back(&presentationTime); + item.emplace_back(&blankFrameInsertion); + if(used(screenFrameRate) && app().emuScreen().supportedFrameRates().size() > 1) + item.emplace_back(&screenFrameRate); +} + +bool FrameTimingView::onFrameTimeChange(VideoSystem vidSys, SteadyClockTime time) +{ + if(!app().outputTimingManager.setFrameTimeOption(vidSys, time)) + { + app().postMessage(4, true, std::format("{:g}Hz not in valid range", toHz(time))); + return false; + } + return true; +} + +} diff --git a/EmuFramework/src/gui/FrameTimingView.hh b/EmuFramework/src/gui/FrameTimingView.hh new file mode 100644 index 000000000..5c37562d2 --- /dev/null +++ b/EmuFramework/src/gui/FrameTimingView.hh @@ -0,0 +1,62 @@ +#pragma once + +/* This file is part of EmuFramework. + + Imagine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Imagine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with EmuFramework. If not, see */ + +#include +#include +#include +#include +#include + +namespace EmuEx +{ + +using namespace IG; +class EmuVideoLayer; +class EmuVideo; +enum class VideoSystem: uint8_t; + +class FrameTimingView : public TableView, public EmuAppHelper +{ +public: + FrameTimingView(ViewAttachParams attach); + void loadStockItems(); + +protected: + static constexpr int MAX_ASPECT_RATIO_ITEMS = 5; + TextMenuItem frameIntervalItem[5]; + MultiChoiceMenuItem frameInterval; + TextMenuItem frameRateItems[4]; + VideoSystem activeVideoSystem{}; + MultiChoiceMenuItem frameRate; + MultiChoiceMenuItem frameRatePAL; + ConditionalMember frameTimeStats; + TextMenuItem frameClockItems[4]; + MultiChoiceMenuItem frameClock; + ConditionalMember presentModeItems[3]; + ConditionalMember presentMode; + ConditionalMember> screenFrameRateItems; + ConditionalMember screenFrameRate; + ConditionalMember presentationTimeItems[3]; + ConditionalMember presentationTime; + BoolMenuItem blankFrameInsertion; + TextHeadingMenuItem advancedHeading; + StaticArrayList item; + + bool onFrameTimeChange(VideoSystem vidSys, SteadyClockTime time); +}; + +} diff --git a/EmuFramework/src/gui/GUIOptionView.cc b/EmuFramework/src/gui/GUIOptionView.cc index 5839b71e6..c5c36231f 100644 --- a/EmuFramework/src/gui/GUIOptionView.cc +++ b/EmuFramework/src/gui/GUIOptionView.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -55,21 +56,14 @@ GUIOptionView::GUIOptionView(ViewAttachParams attach, bool customMenu): {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 2.0 to 10.0", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 2.0 to 10.0", "", + [this](CollectTextInputView &, auto val) { int scaledIntVal = val * 1000.0; - if(app.setFontSize(scaledIntVal)) - { - fontSize.setSelected(MenuId{scaledIntVal}, *this); - dismissPrevious(); - return true; - } - else - { - app.postErrorMessage("Value not in range"); - return false; - } + app().setFontSize(scaledIntVal); + fontSize.setSelected(MenuId{scaledIntVal}, *this); + dismissPrevious(); + return true; }); return false; }, {.id = defaultMenuId} @@ -217,11 +211,11 @@ GUIOptionView::GUIOptionView(ViewAttachParams attach, bool customMenu): "Max Recent Content Items", std::to_string(app().recentContent.maxRecentContent), attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 1 to 100", std::to_string(app().recentContent.maxRecentContent), - [this](EmuApp &app, auto val) + [this](CollectTextInputView &, auto val) { - app.recentContent.maxRecentContent = val; + app().recentContent.maxRecentContent = val; maxRecentContent.set2ndName(std::to_string(val)); return true; }); @@ -279,11 +273,11 @@ GUIOptionView::GUIOptionView(ViewAttachParams attach, bool customMenu): "Set Window Size", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValuePairRangeInputView(attachParams(), e, + pushAndShowNewCollectValuePairRangeInputView(attachParams(), e, "Input Width & Height", "", - [this](EmuApp &app, auto val) + [this](CollectTextInputView &, auto val) { - app.emuWindow().setSize({val.first, val.second}); + app().emuWindow().setSize({val.first, val.second}); return true; }); } diff --git a/EmuFramework/src/gui/InputManagerView.cc b/EmuFramework/src/gui/InputManagerView.cc index 2544239f2..ca6dd4739 100644 --- a/EmuFramework/src/gui/InputManagerView.cc +++ b/EmuFramework/src/gui/InputManagerView.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include "../InputDeviceData.hh" #include #include @@ -563,12 +564,12 @@ InputManagerDeviceView::InputManagerDeviceView(UTF16String name, ViewAttachParam app().postMessage(2, "Can't rename a built-in profile"); return; } - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input name", devConf.keyConf(inputManager).name, - [this](EmuApp &app, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input name", devConf.keyConf(inputManager).name, + [this](CollectTextInputView &, auto str) { if(customKeyConfigsContainName(inputManager.customKeyConfigs, str)) { - app.postErrorMessage("Another profile is already using this name"); + app().postErrorMessage("Another profile is already using this name"); postDraw(); return false; } @@ -590,12 +591,12 @@ InputManagerDeviceView::InputManagerDeviceView(UTF16String name, ViewAttachParam { .onYes = [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input name", "", - [this](EmuApp &app, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input name", "", + [this](CollectTextInputView &, auto str) { if(customKeyConfigsContainName(inputManager.customKeyConfigs, str)) { - app.postErrorMessage("Another profile is already using this name"); + app().postErrorMessage("Another profile is already using this name"); return false; } devConf.setKeyConfCopiedFromExisting(inputManager, str); diff --git a/EmuFramework/src/gui/MainMenuView.cc b/EmuFramework/src/gui/MainMenuView.cc index 59e687da8..752974bf6 100644 --- a/EmuFramework/src/gui/MainMenuView.cc +++ b/EmuFramework/src/gui/MainMenuView.cc @@ -23,6 +23,7 @@ #include #include #include "RecentContentView.hh" +#include "FrameTimingView.hh" #include #include #include @@ -40,10 +41,10 @@ constexpr SystemLogger log{"AppMenus"}; class OptionCategoryView : public TableView, public EmuAppHelper { public: - OptionCategoryView(ViewAttachParams attach, EmuAudio &audio, EmuVideoLayer &videoLayer); + OptionCategoryView(ViewAttachParams attach); protected: - TextMenuItem subConfig[7]; + TextMenuItem subConfig[8]; }; static void onScanStatus(EmuApp &app, unsigned status, int arg); @@ -81,7 +82,7 @@ MainMenuView::MainMenuView(ViewAttachParams attach, bool customMenu): { if(!system().hasContent()) return; - pushAndShow(EmuApp::makeView(attachParams(), EmuApp::ViewID::SYSTEM_ACTIONS), e); + pushAndShow(app().makeView(attachParams(), EmuApp::ViewID::SYSTEM_ACTIONS), e); } }, recentGames @@ -108,7 +109,7 @@ MainMenuView::MainMenuView(ViewAttachParams attach, bool customMenu): "Options", attach, [this](const Input::Event &e) { - pushAndShow(makeView(*audio, *videoLayer), e); + pushAndShow(makeView(), e); } }, onScreenInputManager @@ -332,12 +333,6 @@ void MainMenuView::loadStandardItems() item.emplace_back(&exitApp); } -void MainMenuView::setAudioVideo(EmuAudio &audio_, EmuVideoLayer &videoLayer_) -{ - audio = &audio_; - videoLayer = &videoLayer_; -} - void MainMenuView::reloadItems() { item.clear(); @@ -345,7 +340,7 @@ void MainMenuView::reloadItems() loadStandardItems(); } -OptionCategoryView::OptionCategoryView(ViewAttachParams attach, EmuAudio &audio, EmuVideoLayer &videoLayer): +OptionCategoryView::OptionCategoryView(ViewAttachParams attach): TableView { "Options", @@ -355,41 +350,46 @@ OptionCategoryView::OptionCategoryView(ViewAttachParams attach, EmuAudio &audio, }, subConfig { + { + "Frame Timing", attach, + [this](const Input::Event &e) + { + pushAndShow(makeView(), e); + } + }, { "Video", attach, - [this, &videoLayer](const Input::Event &e) + [this](const Input::Event &e) { - auto view = EmuApp::makeView(attachParams(), EmuApp::ViewID::VIDEO_OPTIONS); - static_cast(view.get())->setEmuVideoLayer(videoLayer); - pushAndShow(std::move(view), e); + pushAndShow(app().makeView(attachParams(), EmuApp::ViewID::VIDEO_OPTIONS), e); } }, { "Audio", attach, - [this, &audio](const Input::Event &e) + [this](const Input::Event &e) { - pushAndShow(EmuApp::makeView(attachParams(), EmuApp::ViewID::AUDIO_OPTIONS), e); + pushAndShow(app().makeView(attachParams(), EmuApp::ViewID::AUDIO_OPTIONS), e); } }, { "System", attach, [this](const Input::Event &e) { - pushAndShow(EmuApp::makeView(attachParams(), EmuApp::ViewID::SYSTEM_OPTIONS), e); + pushAndShow(app().makeView(attachParams(), EmuApp::ViewID::SYSTEM_OPTIONS), e); } }, { "File Paths", attach, [this](const Input::Event &e) { - pushAndShow(EmuApp::makeView(attachParams(), EmuApp::ViewID::FILE_PATH_OPTIONS), e); + pushAndShow(app().makeView(attachParams(), EmuApp::ViewID::FILE_PATH_OPTIONS), e); } }, { "GUI", attach, [this](const Input::Event &e) { - pushAndShow(EmuApp::makeView(attachParams(), EmuApp::ViewID::GUI_OPTIONS), e); + pushAndShow(app().makeView(attachParams(), EmuApp::ViewID::GUI_OPTIONS), e); } }, { diff --git a/EmuFramework/src/gui/SystemActionsView.cc b/EmuFramework/src/gui/SystemActionsView.cc index 4c5399f09..9a9c38350 100644 --- a/EmuFramework/src/gui/SystemActionsView.cc +++ b/EmuFramework/src/gui/SystemActionsView.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include "AutosaveSlotView.hh" #include "ResetAlertView.hh" #include @@ -57,7 +58,7 @@ SystemActionsView::SystemActionsView(ViewAttachParams attach, bool customMenu): { if(system().hasContent()) { - pushAndShow(EmuApp::makeView(attachParams(), EmuApp::ViewID::LIST_CHEATS), e); + pushAndShow(app().makeView(attachParams(), EmuApp::ViewID::LIST_CHEATS), e); } } }, @@ -138,11 +139,11 @@ SystemActionsView::SystemActionsView(ViewAttachParams attach, bool customMenu): // shortcuts to bundled games not yet supported return; } - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Shortcut Name", system().contentDisplayName(), - [this](EmuApp &app, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Shortcut Name", system().contentDisplayName(), + [this](CollectTextInputView &, auto str) { - appContext().addLauncherIcon(str, app.system().contentLocation()); - app.postMessage(2, false, std::format("Added shortcut:\n{}", str)); + appContext().addLauncherIcon(str, system().contentLocation()); + app().postMessage(2, false, std::format("Added shortcut:\n{}", str)); return true; }); } diff --git a/EmuFramework/src/gui/SystemOptionView.cc b/EmuFramework/src/gui/SystemOptionView.cc index bfd275453..2bf8fbc07 100644 --- a/EmuFramework/src/gui/SystemOptionView.cc +++ b/EmuFramework/src/gui/SystemOptionView.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include "CPUAffinityView.hh" #include #include @@ -35,10 +36,10 @@ SystemOptionView::SystemOptionView(ViewAttachParams attach, bool customMenu): {"15min", attach, {.id = 15}}, {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 720", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 720", "", + [this](CollectTextInputView &, auto val) { - app.autosaveManager.saveTimer.frequency = Minutes{val}; + app().autosaveManager.saveTimer.frequency = Minutes{val}; autosaveTimer.setSelected(MenuId{val}, *this); dismissPrevious(); return true; @@ -108,21 +109,14 @@ SystemOptionView::SystemOptionView(ViewAttachParams attach, bool customMenu): {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input above 1.0 to 20.0", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input above 1.0 to 20.0", "", + [this](CollectTextInputView &, auto val) { auto valAsInt = std::round(val * 100.f); - if(app.setAltSpeed(AltSpeedMode::fast, valAsInt)) - { - fastModeSpeed.setSelected(MenuId{valAsInt}, *this); - dismissPrevious(); - return true; - } - else - { - app.postErrorMessage("Value not in range"); - return false; - } + app().setAltSpeed(AltSpeedMode::fast, valAsInt); + fastModeSpeed.setSelected(MenuId{valAsInt}, *this); + dismissPrevious(); + return true; }); return false; }, {.id = defaultMenuId} @@ -149,11 +143,11 @@ SystemOptionView::SystemOptionView(ViewAttachParams attach, bool customMenu): {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 0.05 up to 1.0", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 0.05 up to 1.0", "", + [this](CollectTextInputView &, auto val) { auto valAsInt = std::round(val * 100.f); - if(app.setAltSpeed(AltSpeedMode::slow, valAsInt)) + if(app().setAltSpeed(AltSpeedMode::slow, valAsInt)) { slowModeSpeed.setSelected(MenuId{valAsInt}, *this); dismissPrevious(); @@ -161,7 +155,7 @@ SystemOptionView::SystemOptionView(ViewAttachParams attach, bool customMenu): } else { - app.postErrorMessage("Value not in range"); + app().postErrorMessage("Value not in range"); return false; } }); @@ -190,11 +184,11 @@ SystemOptionView::SystemOptionView(ViewAttachParams attach, bool customMenu): {"60", attach, {.id = 60}}, {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 50000", std::to_string(app().rewindManager.maxStates), - [this](EmuApp &app, auto val) + [this](CollectTextInputView &, auto val) { - app.rewindManager.updateMaxStates(val); + app().rewindManager.updateMaxStates(val); rewindStates.setSelected(val, *this); dismissPrevious(); return true; @@ -222,11 +216,11 @@ SystemOptionView::SystemOptionView(ViewAttachParams attach, bool customMenu): "Rewind State Interval (Seconds)", std::to_string(app().rewindManager.saveTimer.frequency.count()), attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 1 to 60", std::to_string(app().rewindManager.saveTimer.frequency.count()), - [this](EmuApp &app, auto val) + [this](CollectTextInputView &, auto val) { - app.rewindManager.saveTimer.frequency = Seconds{val}; + app().rewindManager.saveTimer.frequency = Seconds{val}; rewindTimeInterval.set2ndName(std::to_string(val)); return true; }); diff --git a/EmuFramework/src/gui/TouchConfigView.cc b/EmuFramework/src/gui/TouchConfigView.cc index 42f2ba0eb..e5a9b078c 100644 --- a/EmuFramework/src/gui/TouchConfigView.cc +++ b/EmuFramework/src/gui/TouchConfigView.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -80,21 +81,14 @@ class DPadElementConfigView : public TableView, public EmuAppHelper(attachParams(), e, "Input 1.0 to 3.0", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 1.0 to 3.0", "", + [this](CollectTextInputView &, auto val) { int scaledIntVal = val * 100.0; - if(elem.dPad()->setDeadzone(renderer(), scaledIntVal, window())) - { - deadzone.setSelected(MenuId{scaledIntVal}, *this); - dismissPrevious(); - return true; - } - else - { - app.postErrorMessage("Value not in range"); - return false; - } + elem.dPad()->setDeadzone(renderer(), scaledIntVal, window()); + deadzone.setSelected(MenuId{scaledIntVal}, *this); + dismissPrevious(); + return true; }); return false; }, {.id = defaultMenuId} @@ -124,23 +118,16 @@ class DPadElementConfigView : public TableView, public EmuAppHelper(attachParams(), e, "Input 0 to 99.0", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 99.0", "", + [this](CollectTextInputView &, auto val) { val = 100. - val; int scaledIntVal = val * 10.0; val /= 100.; - if(elem.dPad()->setDiagonalSensitivity(renderer(), val)) - { - diagonalSensitivity.setSelected(MenuId{scaledIntVal}, *this); - dismissPrevious(); - return true; - } - else - { - app.postErrorMessage("Value not in range"); - return false; - } + elem.dPad()->setDiagonalSensitivity(renderer(), val); + diagonalSensitivity.setSelected(MenuId{scaledIntVal}, *this); + dismissPrevious(); + return true; }); return false; }, {.id = defaultMenuId} @@ -443,8 +430,8 @@ class ButtonGroupElementConfigView : public TableView, public EmuAppHelper(attachParams(), e, "Input 0 to 8", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 8", "", + [this](CollectTextInputView &, auto val) { elem.buttonGroup()->setSpacing(val, window()); vCtrl.place(); @@ -504,8 +491,8 @@ class ButtonGroupElementConfigView : public TableView, public EmuAppHelper(attachParams(), e, "Input 0 to 30", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 30", "", + [this](CollectTextInputView &, auto val) { elem.buttonGroup()->layout.xPadding = val; vCtrl.place(); @@ -545,8 +532,8 @@ class ButtonGroupElementConfigView : public TableView, public EmuAppHelper(attachParams(), e, "Input 0 to 30", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 30", "", + [this](CollectTextInputView &, auto val) { elem.buttonGroup()->layout.yPadding = val; vCtrl.place(); @@ -828,21 +815,14 @@ TouchConfigView::TouchConfigView(ViewAttachParams attach, VController &vCtrl): {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 3.0 to 30.0", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 3.0 to 30.0", "", + [this](CollectTextInputView &, auto val) { int scaledIntVal = val * 100.0; - if(vController.setButtonSize(scaledIntVal)) - { - size.setSelected(MenuId{scaledIntVal}, *this); - dismissPrevious(); - return true; - } - else - { - app.postErrorMessage("Value not in range"); - return false; - } + vController.setButtonSize(scaledIntVal); + size.setSelected(MenuId{scaledIntVal}, *this); + dismissPrevious(); + return true; }); return false; }, {.id = defaultMenuId} diff --git a/EmuFramework/src/gui/VideoOptionView.cc b/EmuFramework/src/gui/VideoOptionView.cc index d4b92e259..a439e994e 100644 --- a/EmuFramework/src/gui/VideoOptionView.cc +++ b/EmuFramework/src/gui/VideoOptionView.cc @@ -20,13 +20,9 @@ #include #include #include -#include +#include #include "PlaceVideoView.hh" -#include #include -#include -#include -#include #include #include @@ -35,156 +31,6 @@ namespace EmuEx constexpr SystemLogger log{"VideoOptionView"}; -class DetectFrameRateView final: public View, public EmuAppHelper -{ -public: - using DetectFrameRateDelegate = DelegateFunc; - DetectFrameRateDelegate onDetectFrameTime; - IG::OnFrameDelegate detectFrameRate; - SteadyClockTime totalFrameTime{}; - SteadyClockTimePoint lastFrameTimestamp{}; - Gfx::Text fpsText; - int allTotalFrames{}; - int callbacks{}; - std::vector frameTimeSample{}; - bool useRenderTaskTime = false; - - DetectFrameRateView(ViewAttachParams attach): View(attach), - fpsText{attach.rendererTask, &defaultFace()} - { - defaultFace().precacheAlphaNum(attach.renderer()); - defaultFace().precache(attach.renderer(), "."); - fpsText.resetString("Preparing to detect frame rate..."); - useRenderTaskTime = !screen()->supportsTimestamps(); - frameTimeSample.reserve(std::round(screen()->frameRate() * 2.)); - } - - ~DetectFrameRateView() final - { - window().setIntendedFrameRate(0); - app().setCPUNeedsLowLatency(appContext(), false); - window().removeOnFrame(detectFrameRate); - } - - void place() final - { - fpsText.compile(); - } - - bool inputEvent(const Input::Event &e) final - { - if(e.keyEvent() && e.keyEvent()->pushed(Input::DefaultKey::CANCEL)) - { - log.info("aborted detection"); - dismiss(); - return true; - } - return false; - } - - void draw(Gfx::RendererCommands &__restrict__ cmds) final - { - using namespace IG::Gfx; - cmds.basicEffect().enableAlphaTexture(cmds); - fpsText.draw(cmds, viewRect().center(), C2DO, ColorName::WHITE); - } - - bool runFrameTimeDetection(SteadyClockTime timestampDiff, double slack) - { - const int framesToTime = frameTimeSample.capacity() * 10; - allTotalFrames++; - frameTimeSample.emplace_back(timestampDiff); - if(frameTimeSample.size() == frameTimeSample.capacity()) - { - bool stableFrameTime = true; - SteadyClockTime frameTimeTotal{}; - { - SteadyClockTime lastFrameTime{}; - for(auto frameTime : frameTimeSample) - { - frameTimeTotal += frameTime; - if(!stableFrameTime) - continue; - double frameTimeDiffSecs = - std::abs(IG::FloatSeconds(lastFrameTime - frameTime).count()); - if(lastFrameTime.count() && frameTimeDiffSecs > slack) - { - log.info("frame times differed by:{}", frameTimeDiffSecs); - stableFrameTime = false; - } - lastFrameTime = frameTime; - } - } - auto frameTimeTotalSecs = FloatSeconds(frameTimeTotal); - auto detectedFrameTimeSecs = frameTimeTotalSecs / (double)frameTimeSample.size(); - auto detectedFrameTime = round(detectedFrameTimeSecs); - { - if(detectedFrameTime.count()) - fpsText.resetString(std::format("{:g}fps", toHz(detectedFrameTimeSecs))); - else - fpsText.resetString("0fps"); - fpsText.compile(); - } - if(stableFrameTime) - { - log.info("found frame time:{}", detectedFrameTimeSecs); - onDetectFrameTime(detectedFrameTime); - dismiss(); - return false; - } - frameTimeSample.erase(frameTimeSample.cbegin()); - postDraw(); - } - else - { - //log.info("waiting for capacity:{}/{}", frameTimeSample.size(), frameTimeSample.capacity()); - } - if(allTotalFrames >= framesToTime) - { - onDetectFrameTime(SteadyClockTime{}); - dismiss(); - return false; - } - else - { - if(useRenderTaskTime) - postDraw(); - return true; - } - } - - void onAddedToController(ViewController *, const Input::Event &e) final - { - lastFrameTimestamp = SteadyClock::now(); - detectFrameRate = - [this](IG::FrameParams params) - { - const int callbacksToSkip = 10; - callbacks++; - if(callbacks < callbacksToSkip) - { - if(useRenderTaskTime) - postDraw(); - return true; - } - return runFrameTimeDetection(params.timestamp - std::exchange(lastFrameTimestamp, params.timestamp), 0.00175); - }; - window().addOnFrame(detectFrameRate); - app().setCPUNeedsLowLatency(appContext(), true); - } -}; - -static std::string makeFrameRateStr(VideoSystem vidSys, const OutputTimingManager &mgr) -{ - auto frameTimeOpt = mgr.frameTimeOption(vidSys); - if(frameTimeOpt == OutputTimingManager::autoOption) - return "Auto"; - else if(frameTimeOpt == OutputTimingManager::originalOption) - return "Original"; - else - return std::format("{:g}Hz", toHz(frameTimeOpt)); -} - static const char *autoWindowPixelFormatStr(IG::ApplicationContext ctx) { return ctx.defaultWindowPixelFormat() == PIXEL_RGB565 ? "RGB565" : "RGBA8888"; @@ -200,8 +46,9 @@ constexpr Gfx::DrawableConfig unpackDrawableConfig(uint16_t c) return {PixelFormatID(c & 0xFF), Gfx::ColorSpace(c >> sizeof(Gfx::DrawableConfig::colorSpace) * 8)}; } -VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): +VideoOptionView::VideoOptionView(ViewAttachParams attach, EmuVideoLayer &videoLayer_, bool customMenu): TableView{"Video Options", attach, item}, + videoLayer{videoLayer_}, textureBufferModeItem { [&] @@ -233,130 +80,6 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): MenuId{renderer().makeValidTextureBufferMode(app().textureBufferMode)}, textureBufferModeItem }, - frameIntervalItem - { - {"Full (No Skip)", attach, {.id = 0}}, - {"Full", attach, {.id = 1}}, - {"1/2", attach, {.id = 2}}, - {"1/3", attach, {.id = 3}}, - {"1/4", attach, {.id = 4}}, - }, - frameInterval - { - "Frame Rate Target", attach, - MenuId{app().frameInterval}, - frameIntervalItem, - MultiChoiceMenuItem::Config - { - .defaultItemOnSelect = [this](TextMenuItem &item) { app().frameInterval.setUnchecked(item.id); } - }, - }, - frameRateItems - { - {"Auto (Match screen when rates are similar)", attach, - [this] - { - if(!app().viewController().emuWindowScreen()->frameRateIsReliable()) - { - app().postErrorMessage("Reported rate potentially unreliable, " - "using the detected rate may give better results"); - } - onFrameTimeChange(activeVideoSystem, OutputTimingManager::autoOption); - }, {.id = OutputTimingManager::autoOption.count()} - }, - {"Original (Use emulated system's rate)", attach, - [this] - { - onFrameTimeChange(activeVideoSystem, OutputTimingManager::originalOption); - }, {.id = OutputTimingManager::originalOption.count()} - }, - {"Detect Custom Rate", attach, - [this](const Input::Event &e) - { - window().setIntendedFrameRate(system().frameRate()); - auto frView = makeView(); - frView->onDetectFrameTime = - [this](SteadyClockTime frameTime) - { - if(frameTime.count()) - { - if(onFrameTimeChange(activeVideoSystem, frameTime)) - dismissPrevious(); - } - else - { - app().postErrorMessage("Detected rate too unstable to use"); - } - }; - pushAndShowModal(std::move(frView), e); - return false; - } - }, - {"Custom Rate", attach, - [this](const Input::Event &e) - { - app().pushAndShowNewCollectValueInputView>(attachParams(), e, - "Input decimal or fraction", "", - [this](EmuApp &, auto val) - { - if(onFrameTimeChange(activeVideoSystem, fromSeconds(val.second / val.first))) - { - if(activeVideoSystem == VideoSystem::NATIVE_NTSC) - frameRate.setSelected(defaultMenuId, *this); - else - frameRatePAL.setSelected(defaultMenuId, *this); - dismissPrevious(); - return true; - } - else - return false; - }); - return false; - }, {.id = defaultMenuId} - }, - }, - frameRate - { - "Frame Rate", attach, - app().outputTimingManager.frameTimeOptionAsMenuId(VideoSystem::NATIVE_NTSC), - frameRateItems, - { - .onSetDisplayString = [this](auto idx, Gfx::Text &t) - { - t.resetString(makeFrameRateStr(VideoSystem::NATIVE_NTSC, app().outputTimingManager)); - return true; - }, - .onSelect = [this](MultiChoiceMenuItem &item, View &view, const Input::Event &e) - { - activeVideoSystem = VideoSystem::NATIVE_NTSC; - item.defaultOnSelect(view, e); - }, - }, - }, - frameRatePAL - { - "Frame Rate (PAL)", attach, - app().outputTimingManager.frameTimeOptionAsMenuId(VideoSystem::PAL), - frameRateItems, - { - .onSetDisplayString = [this](auto idx, Gfx::Text &t) - { - t.resetString(makeFrameRateStr(VideoSystem::PAL, app().outputTimingManager)); - return true; - }, - .onSelect = [this](MultiChoiceMenuItem &item, View &view, const Input::Event &e) - { - activeVideoSystem = VideoSystem::PAL; - item.defaultOnSelect(view, e); - }, - }, - }, - frameTimeStats - { - "Show Frame Time Stats", attach, - app().showFrameTimeStats, - [this](BoolMenuItem &item) { app().showFrameTimeStats = item.flipBoolValue(*this); } - }, aspectRatioItem { [&]() @@ -382,12 +105,12 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): }, MenuItem::Config{.id = 0}); aspectRatioItem.emplace_back("Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueInputView>(attachParams(), e, + pushAndShowNewCollectValueInputView>(attachParams(), e, "Input decimal or fraction", "", - [this](EmuApp &app, auto val) + [this](CollectTextInputView &, auto val) { float ratio = val.first / val.second; - if(app.setVideoAspectRatio(ratio)) + if(app().setVideoAspectRatio(ratio)) { aspectRatio.setSelected(std::bit_cast(ratio), *this); dismissPrevious(); @@ -395,7 +118,7 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): } else { - app.postErrorMessage("Value not in range"); + app().postErrorMessage("Value not in range"); return false; } }); @@ -421,21 +144,21 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): } }, }, - zoomItem + contentScaleItems { {"100%", attach, {.id = 100}}, {"90%", attach, {.id = 90}}, {"80%", attach, {.id = 80}}, - {"Integer-only", attach, {.id = optionImageZoomIntegerOnly}}, - {"Integer-only (Height)", attach, {.id = optionImageZoomIntegerOnlyY}}, + {"Integer-only", attach, {.id = optionContentScaleIntegerOnly}}, + {"Integer-only (Height)", attach, {.id = optionContentScaleIntegerOnlyY}}, {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 10 to 200", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 10 to 200", "", + [this](CollectTextInputView &, auto val) { - app.setVideoZoom(val); - zoom.setSelected(MenuId{val}, *this); + app().setContentScale(val); + contentScale.setSelected(MenuId{val}, *this); dismissPrevious(); return true; }); @@ -443,25 +166,25 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): }, {.id = defaultMenuId} }, }, - zoom + contentScale { - "Content Zoom", attach, - MenuId{app().imageZoom}, - zoomItem, + "Content Scale", attach, + MenuId{app().contentScale}, + contentScaleItems, { .onSetDisplayString = [this](auto idx, Gfx::Text &t) { - if(app().imageZoom <= 200) + if(app().contentScale <= 200) { - t.resetString(std::format("{}%", app().imageZoom.value())); + t.resetString(std::format("{}%", app().contentScale.value())); return true; } return false; }, - .defaultItemOnSelect = [this](TextMenuItem &item) { app().setVideoZoom(item.id); } + .defaultItemOnSelect = [this](TextMenuItem &item) { app().setContentScale(item.id); } }, }, - viewportZoomItem + menuScaleItems { {"100%", attach, {.id = 100}}, {"95%", attach, {.id = 95}}, @@ -469,11 +192,11 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 50 to 100", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 50 to 100", "", + [this](CollectTextInputView &, auto val) { - app.setViewportZoom(val); - viewportZoom.setSelected(MenuId{val}, *this); + app().setMenuScale(val); + menuScale.setSelected(MenuId{val}, *this); dismissPrevious(); return true; }); @@ -481,18 +204,18 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): }, {.id = defaultMenuId} }, }, - viewportZoom + menuScale { - "App Zoom", attach, - MenuId{app().viewportZoom}, - viewportZoomItem, + "Menu Scale", attach, + MenuId{app().menuScale}, + menuScaleItems, { .onSetDisplayString = [this](auto idx, Gfx::Text &t) { - t.resetString(std::format("{}%", app().viewportZoom.value())); + t.resetString(std::format("{}%", app().menuScale.value())); return true; }, - .defaultItemOnSelect = [this](TextMenuItem &item) { app().setViewportZoom(item.id); } + .defaultItemOnSelect = [this](TextMenuItem &item) { app().setMenuScale(item.id); } }, }, contentRotationItem @@ -519,17 +242,17 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): { if(!system().hasContent()) return; - pushAndShowModal(makeView(*videoLayer, app().defaultVController()), e); + pushAndShowModal(makeView(videoLayer, app().defaultVController()), e); } }, imgFilter { "Image Interpolation", attach, - app().videoLayer.usingLinearFilter(), + videoLayer_.usingLinearFilter(), "None", "Linear", [this](BoolMenuItem &item) { - videoLayer->setLinearFilter(item.flipBoolValue(*this)); + videoLayer.setLinearFilter(item.flipBoolValue(*this)); app().viewController().postDrawToEmuWindows(); } }, @@ -545,12 +268,12 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): imgEffect { "Image Effect", attach, - MenuId{app().videoLayer.effectId()}, + MenuId{videoLayer_.effectId()}, imgEffectItem, { .defaultItemOnSelect = [this](TextMenuItem &item) { - videoLayer->setEffect(system(), ImageEffectId(item.id.val), app().videoEffectPixelFormat()); + videoLayer.setEffect(system(), ImageEffectId(item.id.val), app().videoEffectPixelFormat()); app().viewController().postDrawToEmuWindows(); } }, @@ -569,12 +292,12 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): overlayEffect { "Overlay Effect", attach, - MenuId{app().videoLayer.overlayEffectId()}, + MenuId{videoLayer_.overlayEffectId()}, overlayEffectItem, { .defaultItemOnSelect = [this](TextMenuItem &item) { - videoLayer->setOverlay(ImageOverlayId(item.id.val)); + videoLayer.setOverlay(ImageOverlayId(item.id.val)); app().viewController().postDrawToEmuWindows(); } }, @@ -588,11 +311,11 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): {"Custom Value", attach, [this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 100", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 100", "", + [this](CollectTextInputView &, auto val) { - videoLayer->setOverlayIntensity(val / 100.f); - app.viewController().postDrawToEmuWindows(); + videoLayer.setOverlayIntensity(val / 100.f); + app().viewController().postDrawToEmuWindows(); overlayEffectLevel.setSelected(MenuId{val}, *this); dismissPrevious(); return true; @@ -604,17 +327,17 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): overlayEffectLevel { "Overlay Effect Level", attach, - MenuId{app().videoLayer.overlayIntensity() * 100.f}, + MenuId{videoLayer_.overlayIntensity() * 100.f}, overlayEffectLevelItem, { .onSetDisplayString = [this](auto idx, Gfx::Text &t) { - t.resetString(std::format("{}%", int(videoLayer->overlayIntensity() * 100.f))); + t.resetString(std::format("{}%", int(videoLayer.overlayIntensity() * 100.f))); return true; }, .defaultItemOnSelect = [this](TextMenuItem &item) { - videoLayer->setOverlayIntensity(item.id / 100.f); + videoLayer.setOverlayIntensity(item.id / 100.f); app().viewController().postDrawToEmuWindows(); } }, @@ -644,7 +367,7 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): .defaultItemOnSelect = [this](TextMenuItem &item) { app().imageEffectPixelFormat = PixelFormatID(item.id.val); - videoLayer->setEffectFormat(app().videoEffectPixelFormat()); + videoLayer.setEffectFormat(app().videoEffectPixelFormat()); app().viewController().postDrawToEmuWindows(); } }, @@ -712,55 +435,6 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): app().setEmuViewOnExtraWindow(app().showOnSecondScreen, *appContext().screens()[1]); } }, - frameClockItems - { - {"Auto", attach, MenuItem::Config{.id = FrameTimeSource::Unset}}, - {"Screen (Less latency & power use)", attach, MenuItem::Config{.id = FrameTimeSource::Screen}}, - {"Renderer (May buffer multiple frames)", attach, MenuItem::Config{.id = FrameTimeSource::Renderer}}, - }, - frameClock - { - "Frame Clock", attach, - MenuId{FrameTimeSource(app().frameTimeSource)}, - frameClockItems, - MultiChoiceMenuItem::Config - { - .onSetDisplayString = [this](auto idx, Gfx::Text &t) - { - t.resetString(wise_enum::to_string(app().effectiveFrameTimeSource())); - return true; - }, - .defaultItemOnSelect = [this](TextMenuItem &item) - { - app().frameTimeSource = FrameTimeSource(item.id.val); - app().video.resetImage(); // texture can switch between single/double buffered - } - }, - }, - presentModeItems - { - {"Auto", attach, MenuItem::Config{.id = Gfx::PresentMode::Auto}}, - {"Immediate (Less compositor latency, may drop frames)", attach, MenuItem::Config{.id = Gfx::PresentMode::Immediate}}, - {"Queued (Better frame rate stability)", attach, MenuItem::Config{.id = Gfx::PresentMode::FIFO}}, - }, - presentMode - { - "Present Mode", attach, - MenuId{Gfx::PresentMode(app().presentMode)}, - presentModeItems, - MultiChoiceMenuItem::Config - { - .onSetDisplayString = [this](auto idx, Gfx::Text &t) - { - t.resetString(renderer().evalPresentMode(app().emuWindow(), app().presentMode) == Gfx::PresentMode::FIFO ? "Queued" : "Immediate"); - return true; - }, - .defaultItemOnSelect = [this](TextMenuItem &item) - { - app().presentMode = Gfx::PresentMode(item.id.val); - } - }, - }, renderPixelFormatItem { {"Auto (Match display format)", attach, {.id = PIXEL_NONE}}, @@ -785,56 +459,6 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): .defaultItemOnSelect = [this](TextMenuItem &item) { app().setRenderPixelFormat(PixelFormatID(item.id.val)); } }, }, - screenFrameRateItems - { - [&] - { - std::vector items; - auto setRateDel = [this](TextMenuItem &item) { app().overrideScreenFrameRate = std::bit_cast(item.id); }; - items.emplace_back("Off", attach, setRateDel, MenuItem::Config{.id = 0}); - for(auto rate : app().emuScreen().supportedFrameRates()) - items.emplace_back(std::format("{:g}Hz", rate), attach, setRateDel, MenuItem::Config{.id = std::bit_cast(rate)}); - return items; - }() - }, - screenFrameRate - { - "Override Screen Frame Rate", attach, - std::bit_cast(FrameRate(app().overrideScreenFrameRate)), - screenFrameRateItems - }, - presentationTimeItems - { - {"Full (Apply to all frame rate targets)", attach, MenuItem::Config{.id = PresentationTimeMode::full}}, - {"Basic (Only apply to lower frame rate targets)", attach, MenuItem::Config{.id = PresentationTimeMode::basic}}, - {"Off", attach, MenuItem::Config{.id = PresentationTimeMode::off}}, - }, - presentationTime - { - "Precise Frame Pacing", attach, - MenuId{PresentationTimeMode(app().presentationTimeMode)}, - presentationTimeItems, - MultiChoiceMenuItem::Config - { - .onSetDisplayString = [this](auto idx, Gfx::Text &t) - { - if(app().presentationTimeMode == PresentationTimeMode::off) - return false; - t.resetString(app().presentationTimeMode == PresentationTimeMode::full ? "Full" : "Basic"); - return true; - }, - .defaultItemOnSelect = [this](TextMenuItem &item) - { - app().presentationTimeMode = PresentationTimeMode(item.id.val); - } - }, - }, - blankFrameInsertion - { - "Allow Blank Frame Insertion", attach, - app().allowBlankFrameInsertion, - [this](BoolMenuItem &item) { app().allowBlankFrameInsertion = item.flipBoolValue(*this); } - }, brightnessItem { { @@ -909,8 +533,6 @@ VideoOptionView::VideoOptionView(ViewAttachParams attach, bool customMenu): } }, }, - visualsHeading{"Visuals", attach}, - screenShapeHeading{"Screen Shape", attach}, colorLevelsHeading{"Color Levels", attach}, advancedHeading{"Advanced", attach}, systemSpecificHeading{"System-specific", attach} @@ -927,25 +549,14 @@ void VideoOptionView::place() TableView::place(); } - void VideoOptionView::loadStockItems() { - item.emplace_back(&frameInterval); - item.emplace_back(&frameRate); - if(EmuSystem::hasPALVideoSystem) - { - item.emplace_back(&frameRatePAL); - } - if(used(frameTimeStats)) - item.emplace_back(&frameTimeStats); - item.emplace_back(&visualsHeading); item.emplace_back(&imgFilter); item.emplace_back(&imgEffect); item.emplace_back(&overlayEffect); item.emplace_back(&overlayEffectLevel); - item.emplace_back(&screenShapeHeading); - item.emplace_back(&zoom); - item.emplace_back(&viewportZoom); + item.emplace_back(&contentScale); + item.emplace_back(&menuScale); item.emplace_back(&aspectRatio); item.emplace_back(&contentRotation); placeVideo.setActive(system().hasContent()); @@ -964,43 +575,20 @@ void VideoOptionView::loadStockItems() if(EmuSystem::canRenderRGBA8888) item.emplace_back(&renderPixelFormat); item.emplace_back(&imgEffectPixelFormat); - item.emplace_back(&frameClock); - if(used(presentMode)) - item.emplace_back(&presentMode); - if(used(presentationTime) && renderer().supportsPresentationTime()) - item.emplace_back(&presentationTime); - item.emplace_back(&blankFrameInsertion); - if(used(screenFrameRate) && app().emuScreen().supportedFrameRates().size() > 1) - item.emplace_back(&screenFrameRate); if(used(secondDisplay)) item.emplace_back(&secondDisplay); if(used(showOnSecondScreen) && app().supportsShowOnSecondScreen(appContext())) item.emplace_back(&showOnSecondScreen); } -void VideoOptionView::setEmuVideoLayer(EmuVideoLayer &videoLayer_) -{ - videoLayer = &videoLayer_; -} - -bool VideoOptionView::onFrameTimeChange(VideoSystem vidSys, SteadyClockTime time) -{ - if(!app().outputTimingManager.setFrameTimeOption(vidSys, time)) - { - app().postMessage(4, true, std::format("{:g}Hz not in valid range", toHz(time))); - return false; - } - return true; -} - TextMenuItem::SelectDelegate VideoOptionView::setVideoBrightnessCustomDel(ImageChannel ch) { return [=, this](const Input::Event &e) { - app().pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 200", "", - [=, this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 200", "", + [=, this](CollectTextInputView &, auto val) { - app.setVideoBrightness(val / 100.f, ch); + app().setVideoBrightness(val / 100.f, ch); if(ch == ImageChannel::All) setAllColorLevelsSelected(MenuId{val}); else @@ -1031,7 +619,7 @@ void VideoOptionView::setAllColorLevelsSelected(MenuId val) EmuVideo &VideoOptionView::emuVideo() const { - return videoLayer->emuVideo(); + return videoLayer.video; } } diff --git a/GBA.emu/src/main/Cheats.cc b/GBA.emu/src/main/Cheats.cc index 3b3b3273d..6c5130b20 100644 --- a/GBA.emu/src/main/Cheats.cc +++ b/GBA.emu/src/main/Cheats.cc @@ -15,6 +15,7 @@ #include #include +#include #include "EmuCheatViews.hh" #include "MainSystem.hh" #include @@ -105,7 +106,7 @@ void EmuEditCheatListView::addNewCheat(int isGSv3) app().postMessage(true, "Too many cheats, delete some first"); return; } - app().pushAndShowNewCollectTextInputView(attachParams(), {}, + pushAndShowNewCollectTextInputView(attachParams(), {}, isGSv3 ? "Input xxxxxxxx yyyyyyyy" : "Input xxxxxxxx yyyyyyyy (GS) or xxxxxxxx yyyy (AR)", "", [this, isGSv3](CollectTextInputView &view, const char *str) { @@ -133,7 +134,7 @@ void EmuEditCheatListView::addNewCheat(int isGSv3) onCheatListChanged(); writeCheatFile(system()); view.dismiss(); - app().pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", + pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", [this](CollectTextInputView &view, const char *str) { if(str) diff --git a/GBA.emu/src/main/EmuMenuViews.cc b/GBA.emu/src/main/EmuMenuViews.cc index d13adbf32..6ec00dafd 100644 --- a/GBA.emu/src/main/EmuMenuViews.cc +++ b/GBA.emu/src/main/EmuMenuViews.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include "EmuCheatViews.hh" #include "MainApp.hh" #include @@ -255,8 +256,8 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper(attachParams(), e, "Input 0 to 100", "", - [this, gbVol](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 100", "", + [this, gbVol](CollectTextInputView&, auto val) { soundSetVolume(gGba, val / 100.f, gbVol); size_t idx = gbVol ? 1 : 0; @@ -335,8 +336,8 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper(attachParams(), e, "Input 0 to 100", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 100", "", + [this](CollectTextInputView&, auto val) { soundSetFiltering(gGba, val / 100.f); filteringLevel.setSelected(MenuId{val}, *this); @@ -373,7 +374,7 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper(attachParams(), e, "Input 0 to 50000", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 50000", "", + [this](CollectTextInputView&, auto val) { system().lightSensorScaleLux = val; lightSensorScale.setSelected(MenuId{val}, *this); @@ -536,7 +537,7 @@ std::unique_ptr EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) { case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); case ViewID::EDIT_CHEATS: return std::make_unique(attach); case ViewID::LIST_CHEATS: return std::make_unique(attach); diff --git a/GBC.emu/src/main/Cheats.cc b/GBC.emu/src/main/Cheats.cc index 119e79210..0598560e2 100644 --- a/GBC.emu/src/main/Cheats.cc +++ b/GBC.emu/src/main/Cheats.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include "EmuCheatViews.hh" #include "MainSystem.hh" #include
@@ -177,19 +178,19 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, GbcCheat &cheat_, Re attach, [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, + pushAndShowNewCollectValueInputView(attachParams(), e, "Input xxxxxxxx (GS) or xxx-xxx-xxx (GG) code", cheat->code, - [this](EmuApp &app, auto str) + [this](CollectTextInputView&, auto str) { if(!strIsGGCode(str) && !strIsGSCode(str)) { - app.postMessage(true, "Invalid format"); + app().postMessage(true, "Invalid format"); postDraw(); return false; } cheat->code = IG::toUpperCasecode)>(str); writeCheatFile(system()); - static_cast(app.system()).applyCheats(); + static_cast(app().system()).applyCheats(); ggCode.set2ndName(str); ggCode.compile(); postDraw(); @@ -233,7 +234,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): "Add Game Genie / GameShark Code", attach, [this](TextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectTextInputView(attachParams(), e, + pushAndShowNewCollectTextInputView(attachParams(), e, "Input xxxxxxxx (GS) or xxx-xxx-xxx (GG) code", "", [this](CollectTextInputView &view, const char *str) { @@ -259,7 +260,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): onCheatListChanged(); writeCheatFile(system()); view.dismiss(); - app().pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", + pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", [this](CollectTextInputView &view, const char *str) { if(str) diff --git a/GBC.emu/src/main/EmuMenuViews.cc b/GBC.emu/src/main/EmuMenuViews.cc index 4860f392e..f125e99ab 100644 --- a/GBC.emu/src/main/EmuMenuViews.cc +++ b/GBC.emu/src/main/EmuMenuViews.cc @@ -48,7 +48,7 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) { switch(id) { - case ViewID::VIDEO_OPTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::VIDEO_OPTIONS: return std::make_unique(attach, videoLayer); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); case ViewID::EDIT_CHEATS: return std::make_unique(attach); diff --git a/Lynx.emu/src/main/EmuMenuViews.cc b/Lynx.emu/src/main/EmuMenuViews.cc index b69955a55..5e99959c0 100644 --- a/Lynx.emu/src/main/EmuMenuViews.cc +++ b/Lynx.emu/src/main/EmuMenuViews.cc @@ -136,7 +136,7 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) { case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); default: return nullptr; } diff --git a/MD.emu/src/main/Cheats.cc b/MD.emu/src/main/Cheats.cc index 8baa8228a..fafe05f98 100644 --- a/MD.emu/src/main/Cheats.cc +++ b/MD.emu/src/main/Cheats.cc @@ -14,6 +14,7 @@ along with MD.emu. If not, see */ #include +#include #include
#include #include "EmuCheatViews.hh" @@ -531,8 +532,8 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, MdCheat &cheat_, Ref attach, [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, emuSystemIs16Bit() ? INPUT_CODE_16BIT_STR : INPUT_CODE_8BIT_STR, cheat->code, - [this](EmuApp &, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, emuSystemIs16Bit() ? INPUT_CODE_16BIT_STR : INPUT_CODE_8BIT_STR, cheat->code, + [this](CollectTextInputView&, auto str) { cheat->code = IG::toUpperCasecode)>(str); if(!decodeCheat(cheat->code.data(), cheat->address, cheat->data, cheat->origData)) @@ -605,7 +606,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): "Add Game Genie / Action Replay Code", attach, [this](TextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectTextInputView(attachParams(), e, emuSystemIs16Bit() ? INPUT_CODE_16BIT_STR : INPUT_CODE_8BIT_STR, "", + pushAndShowNewCollectTextInputView(attachParams(), e, emuSystemIs16Bit() ? INPUT_CODE_16BIT_STR : INPUT_CODE_8BIT_STR, "", [this](CollectTextInputView &view, const char *str) { if(str) @@ -635,7 +636,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): onCheatListChanged(); writeCheatFile(system()); view.dismiss(); - app().pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", + pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", [this](CollectTextInputView &view, const char *str) { if(str) diff --git a/MD.emu/src/main/EmuMenuViews.cc b/MD.emu/src/main/EmuMenuViews.cc index 8800f6ed8..fbfa5980b 100644 --- a/MD.emu/src/main/EmuMenuViews.cc +++ b/MD.emu/src/main/EmuMenuViews.cc @@ -238,7 +238,7 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) { switch(id) { - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); diff --git a/MSX.emu/src/main/EmuMenuViews.cc b/MSX.emu/src/main/EmuMenuViews.cc index 4da4414b0..eb8badfb7 100644 --- a/MSX.emu/src/main/EmuMenuViews.cc +++ b/MSX.emu/src/main/EmuMenuViews.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -725,8 +726,8 @@ class SoundMixerView : public TableView, public MainAppHelper TextMenuItem{"Custom Value", attachParams(), [this, type = (uint8_t)type, idx](Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 0 to 100", "", - [this, type, idx](EmuApp &app, auto val) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 0 to 100", "", + [this, type, idx](CollectTextInputView&, auto val) { if(val >= 0 && val <= 100) { @@ -737,7 +738,7 @@ class SoundMixerView : public TableView, public MainAppHelper } else { - app.postErrorMessage("Value not in range"); + app().postErrorMessage("Value not in range"); return false; } }); @@ -798,8 +799,8 @@ class SoundMixerView : public TableView, public MainAppHelper TextMenuItem{"Custom Value", attachParams(), [this, type = (uint8_t)type, idx](Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 0 to 100", "", - [this, type, idx](EmuApp &app, auto val) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 0 to 100", "", + [this, type, idx](CollectTextInputView&, auto val) { if(val >= 0 && val <= 100) { @@ -810,7 +811,7 @@ class SoundMixerView : public TableView, public MainAppHelper } else { - app.postErrorMessage("Value not in range"); + app().postErrorMessage("Value not in range"); return false; } }); @@ -895,7 +896,7 @@ class SoundMixerView : public TableView, public MainAppHelper class CustomAudioOptionView : public AudioOptionView { public: - CustomAudioOptionView(ViewAttachParams attach): AudioOptionView{attach, true} + CustomAudioOptionView(ViewAttachParams attach, EmuAudio& audio): AudioOptionView{attach, audio, true} { loadStockItems(); item.emplace_back(&mixer); @@ -918,7 +919,7 @@ std::unique_ptr EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) { case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); default: return nullptr; } diff --git a/NES.emu/src/main/Cheats.cc b/NES.emu/src/main/Cheats.cc index 048a14f8d..6f74a8054 100644 --- a/NES.emu/src/main/Cheats.cc +++ b/NES.emu/src/main/Cheats.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include "EmuCheatViews.hh" #include #include @@ -98,14 +99,14 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, unsigned cheatIdx, R attachParams(), [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 4-digit hex", addrStr, - [this](EmuApp &app, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 4-digit hex", addrStr, + [this](CollectTextInputView&, auto str) { unsigned a = strtoul(str, nullptr, 16); if(a > 0xFFFF) { logMsg("addr 0x%X too large", a); - app.postMessage(true, "Invalid input"); + app().postMessage(true, "Invalid input"); postDraw(); return false; } @@ -125,14 +126,14 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, unsigned cheatIdx, R attachParams(), [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 2-digit hex", valueStr, - [this](EmuApp &app, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 2-digit hex", valueStr, + [this](CollectTextInputView&, auto str) { unsigned a = strtoul(str, nullptr, 16); if(a > 0xFF) { logMsg("val 0x%X too large", a); - app.postMessage(true, "Invalid input"); + app().postMessage(true, "Invalid input"); postDraw(); return false; } @@ -152,7 +153,7 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, unsigned cheatIdx, R attachParams(), [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectTextInputView(attachParams(), e, "Input 2-digit hex or blank", compStr.data(), + pushAndShowNewCollectTextInputView(attachParams(), e, "Input 2-digit hex or blank", compStr.data(), [this](CollectTextInputView &view, const char *str) { if(str) @@ -190,12 +191,12 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, unsigned cheatIdx, R attachParams(), [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input Game Genie code", ggCodeStr, - [this](EmuApp &app, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input Game Genie code", ggCodeStr, + [this](CollectTextInputView&, auto str) { if(!isValidGGCodeLen(str)) { - app.postMessage(true, "Invalid, must be 6 or 8 digits"); + app().postMessage(true, "Invalid, must be 6 or 8 digits"); return false; } ggCodeStr = str; @@ -325,7 +326,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): "Add Game Genie Code", attachParams(), [this](TextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectTextInputView(attachParams(), e, "Input Game Genie code", "", + pushAndShowNewCollectTextInputView(attachParams(), e, "Input Game Genie code", "", [this](CollectTextInputView &view, const char *str) { if(str) @@ -354,7 +355,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): logMsg("added new cheat, %d total", fceuCheats); FCEU_FlushGameCheats(nullptr, 0, false); view.dismiss(); - app().pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", + pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", [this](CollectTextInputView &view, const char *str) { if(str) @@ -384,7 +385,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): "Add RAM Patch", attachParams(), [this](TextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectTextInputView(attachParams(), e, "Input description", "", + pushAndShowNewCollectTextInputView(attachParams(), e, "Input description", "", [this](CollectTextInputView &view, const char *str) { if(str) diff --git a/NES.emu/src/main/EmuMenuViews.cc b/NES.emu/src/main/EmuMenuViews.cc index 6fd9237a8..6803b9e51 100644 --- a/NES.emu/src/main/EmuMenuViews.cc +++ b/NES.emu/src/main/EmuMenuViews.cc @@ -23,6 +23,7 @@ #include #include #include +#include #include "EmuCheatViews.hh" #include "MainApp.hh" #include @@ -237,9 +238,9 @@ class ConsoleOptionView : public TableView, public MainAppHelper(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 30000", std::to_string(postrenderscanlines), - [this](EmuApp &app, auto val) + [this](CollectTextInputView&, auto val) { system().sessionOptionSet(); postrenderscanlines = val; @@ -254,9 +255,9 @@ class ConsoleOptionView : public TableView, public MainAppHelper(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 16", std::to_string(vblankscanlines), - [this](EmuApp &app, auto val) + [this](CollectTextInputView&, auto val) { system().sessionOptionSet(); vblankscanlines = val; @@ -444,7 +445,7 @@ class CustomVideoOptionView : public VideoOptionView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) switch(id) { case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); - case ViewID::VIDEO_OPTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::VIDEO_OPTIONS: return std::make_unique(attach, videoLayer); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); case ViewID::EDIT_CHEATS: return std::make_unique(attach); diff --git a/PCE.emu/src/main/EmuMenuViews.cc b/PCE.emu/src/main/EmuMenuViews.cc index b97bba6f4..367e8299e 100644 --- a/PCE.emu/src/main/EmuMenuViews.cc +++ b/PCE.emu/src/main/EmuMenuViews.cc @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "MainApp.hh" #include @@ -268,7 +269,7 @@ class CustomVideoOptionView : public VideoOptionView, public MainAppHelper(attachParams(), e, "Input 0 to 200", "", - [=, this](EmuApp &, auto val) + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 0 to 200", "", + [=, this](CollectTextInputView&, auto val) { system().setVolume(type, val); volumeLevel[desc(type).idx].setSelected(MenuId{val}, *this); @@ -453,7 +454,7 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) switch(id) { case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); - case ViewID::VIDEO_OPTIONS: return std::make_unique(attach); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); + case ViewID::VIDEO_OPTIONS: return std::make_unique(attach, videoLayer); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); default: return nullptr; diff --git a/Saturn.emu/src/main/EmuMenuViews.cc b/Saturn.emu/src/main/EmuMenuViews.cc index f63e529f6..efdbbc86d 100644 --- a/Saturn.emu/src/main/EmuMenuViews.cc +++ b/Saturn.emu/src/main/EmuMenuViews.cc @@ -626,7 +626,7 @@ class CustomVideoOptionView : public VideoOptionView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); case ViewID::SYSTEM_OPTIONS: return std::make_unique(attach); case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); - case ViewID::VIDEO_OPTIONS: return std::make_unique(attach); + case ViewID::VIDEO_OPTIONS: return std::make_unique(attach, videoLayer); default: return nullptr; } } diff --git a/Snes9x/src/main/Cheats.cc b/Snes9x/src/main/Cheats.cc index 9ea4a1183..023a731ce 100644 --- a/Snes9x/src/main/Cheats.cc +++ b/Snes9x/src/main/Cheats.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include "EmuCheatViews.hh" #include "MainSystem.hh" #include @@ -220,14 +221,14 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, int cheatIdx, Refres attach, [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 6-digit hex", addrStr.data(), - [this](EmuApp &app, auto str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 6-digit hex", addrStr.data(), + [this](CollectTextInputView&, auto str) { unsigned a = strtoul(str, nullptr, 16); if(a > 0xFFFFFF) { logMsg("addr 0x%X too large", a); - app.postMessage(true, "Invalid input"); + app().postMessage(true, "Invalid input"); return false; } addrStr = a ? str : "0"; @@ -256,13 +257,13 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, int cheatIdx, Refres attach, [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectValueInputView(attachParams(), e, "Input 2-digit hex", valueStr.data(), - [this](EmuApp &app, const char *str) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 2-digit hex", valueStr.data(), + [this](CollectTextInputView&, const char *str) { unsigned a = strtoul(str, nullptr, 16); if(a > 0xFF) { - app.postMessage(true, "value must be <= FF"); + app().postMessage(true, "value must be <= FF"); return false; } valueStr = a ? str : "0"; @@ -295,7 +296,7 @@ EmuEditCheatView::EmuEditCheatView(ViewAttachParams attach, int cheatIdx, Refres attach, [this](DualTextMenuItem &item, View &, Input::Event e) { - app().pushAndShowNewCollectTextInputView(attachParams(), e, "Input 2-digit hex or blank", savedStr.data(), + pushAndShowNewCollectTextInputView(attachParams(), e, "Input 2-digit hex or blank", savedStr.data(), [this](CollectTextInputView &view, const char *str) { if(str) @@ -414,7 +415,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): app().postMessage(true, "Too many cheats, delete some first"); return; } - app().pushAndShowNewCollectTextInputView(attachParams(), e, + pushAndShowNewCollectTextInputView(attachParams(), e, "Input xxxx-xxxx (GG), xxxxxxxx (AR), or GF code", "", [this](CollectTextInputView &view, const char *str) { @@ -431,7 +432,7 @@ EmuEditCheatListView::EmuEditCheatListView(ViewAttachParams attach): onCheatListChanged(); writeCheatsFile(system()); view.dismiss(); - app().pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", + pushAndShowNewCollectTextInputView(attachParams(), {}, "Input description", "", [this, idx](CollectTextInputView &view, const char *str) { if(str) diff --git a/Snes9x/src/main/EmuMenuViews.cc b/Snes9x/src/main/EmuMenuViews.cc index f94d9c6e8..5eab7dd75 100644 --- a/Snes9x/src/main/EmuMenuViews.cc +++ b/Snes9x/src/main/EmuMenuViews.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include "EmuCheatViews.hh" #include "MainApp.hh" #include @@ -51,7 +52,7 @@ class CustomAudioOptionView : public AudioOptionView, public MainAppHelper(attachParams(), e, "Input 5 to 250", "", - [this](EmuApp &app, auto val) + pushAndShowNewCollectValueInputView(attachParams(), e, "Input 5 to 250", "", + [this](CollectTextInputView&, auto val) { if(system().optionSuperFXClockMultiplier.isValid(val)) { @@ -209,7 +210,7 @@ class ConsoleOptionView : public TableView, public MainAppHelper EmuApp::makeCustomView(ViewAttachParams attach, ViewID id) switch(id) { #ifndef SNES9X_VERSION_1_4 - case ViewID::AUDIO_OPTIONS: return std::make_unique(attach); + case ViewID::AUDIO_OPTIONS: return std::make_unique(attach, audio); #endif case ViewID::FILE_PATH_OPTIONS: return std::make_unique(attach); case ViewID::SYSTEM_ACTIONS: return std::make_unique(attach); diff --git a/Swan.emu/src/main/EmuMenuViews.cc b/Swan.emu/src/main/EmuMenuViews.cc index c5c7401e5..e2333a001 100644 --- a/Swan.emu/src/main/EmuMenuViews.cc +++ b/Swan.emu/src/main/EmuMenuViews.cc @@ -15,6 +15,7 @@ #include #include +#include #include #include "MainApp.hh" #include @@ -50,14 +51,14 @@ class CustomSystemOptionView : public SystemOptionView, public MainAppHelper(attachParams(), e, + pushAndShowNewCollectValueInputView(attachParams(), e, "Input name", system().userName, - [this](EmuApp &app, auto str_) + [this](CollectTextInputView&, auto str_) { std::string_view str{str_}; if(str.size() > system().userName.max_size()) { - app.postErrorMessage("Name is too long"); + app().postErrorMessage("Name is too long"); return false; } system().userName = str; @@ -72,9 +73,9 @@ class CustomSystemOptionView : public SystemOptionView, public MainAppHelper(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 1 to 9999", std::to_string(system().userProfile.birthYear), - [this](EmuApp &app, auto val) + [this](CollectTextInputView&, auto val) { system().userProfile.birthYear = val; birthYear.set2ndName(std::to_string(val)); @@ -88,9 +89,9 @@ class CustomSystemOptionView : public SystemOptionView, public MainAppHelper(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 1 to 12", std::to_string(system().userProfile.birthMonth), - [this](EmuApp &app, auto val) + [this](CollectTextInputView&, auto val) { system().userProfile.birthMonth = val; birthMonth.set2ndName(std::to_string(val)); @@ -104,9 +105,9 @@ class CustomSystemOptionView : public SystemOptionView, public MainAppHelper(attachParams(), e, + pushAndShowNewCollectValueRangeInputView(attachParams(), e, "Input 1 to 31", std::to_string(system().userProfile.birthDay), - [this](EmuApp &app, auto val) + [this](CollectTextInputView&, auto val) { system().userProfile.birthDay = val; birthDay.set2ndName(std::to_string(val)); diff --git a/imagine/include/imagine/base/Screen.hh b/imagine/include/imagine/base/Screen.hh index c7bbc2102..796fd6f84 100644 --- a/imagine/include/imagine/base/Screen.hh +++ b/imagine/include/imagine/base/Screen.hh @@ -49,6 +49,7 @@ public: bool removeOnFrame(OnFrameDelegate); bool containsOnFrame(OnFrameDelegate) const; size_t onFrameDelegates() const; + void setVariableFrameTime(bool); FrameParams makeFrameParams(SteadyClockTimePoint timestamp) const; bool frameRateIsReliable() const; FrameRate frameRate() const; @@ -76,6 +77,7 @@ private: void unpostFrame(); void postFrameTimer(); void unpostFrameTimer(); + bool shouldUpdateFrameTimer(const FrameTimer&, bool newVariableFrameTimeValue); }; } diff --git a/imagine/include/imagine/base/Window.hh b/imagine/include/imagine/base/Window.hh index 63c00a8d7..d85dc6c05 100644 --- a/imagine/include/imagine/base/Window.hh +++ b/imagine/include/imagine/base/Window.hh @@ -59,11 +59,12 @@ public: void setFormat(PixelFormat); PixelFormat pixelFormat() const; bool operator ==(Window const &rhs) const; - bool addOnFrame(OnFrameDelegate del, FrameTimeSource src = {}, int priority = 0); - bool removeOnFrame(OnFrameDelegate del, FrameTimeSource src = {}); + bool addOnFrame(OnFrameDelegate, FrameTimeSource src = {}, int priority = 0); + bool removeOnFrame(OnFrameDelegate, FrameTimeSource src = {}); bool moveOnFrame(Window &srcWin, OnFrameDelegate, FrameTimeSource src = {}); FrameTimeSource defaultFrameTimeSource() const; FrameTimeSource evalFrameTimeSource(FrameTimeSource) const; + void configureFrameTimeSource(FrameTimeSource); void resetAppData(); void resetRendererData(); bool isMainWindow() const; diff --git a/imagine/include/imagine/base/android/AndroidApplication.hh b/imagine/include/imagine/base/android/AndroidApplication.hh index 6e2c69ba5..b4d427be1 100644 --- a/imagine/include/imagine/base/android/AndroidApplication.hh +++ b/imagine/include/imagine/base/android/AndroidApplication.hh @@ -88,7 +88,7 @@ public: bool openDocumentTreeIntent(JNIEnv *, jobject baseActivity, SystemDocumentPickerDelegate); bool openDocumentIntent(JNIEnv *, jobject baseActivity, SystemDocumentPickerDelegate); bool createDocumentIntent(JNIEnv *, jobject baseActivity, SystemDocumentPickerDelegate); - FrameTimer makeFrameTimer(Screen &); + void emplaceFrameTimer(FrameTimer&, Screen&, bool useVariableTime = {}); bool requestPermission(ApplicationContext, Permission); UniqueFileDescriptor openFileUriFd(JNIEnv *, jobject baseActivity, CStringView uri, OpenFlags oFlags = {}) const; bool fileUriExists(JNIEnv *, jobject baseActivity, CStringView uri) const; diff --git a/imagine/include/imagine/base/android/Choreographer.hh b/imagine/include/imagine/base/android/Choreographer.hh index 04d72c00b..dbfccbad5 100644 --- a/imagine/include/imagine/base/android/Choreographer.hh +++ b/imagine/include/imagine/base/android/Choreographer.hh @@ -64,9 +64,9 @@ template class ChoreographerFrameTimer final { public: + constexpr ChoreographerFrameTimer() = default; ChoreographerFrameTimer(ChoreographerBase &choreographer): - choreographerPtr{&choreographer} - {} + choreographerPtr{&choreographer} {} void scheduleVSync() { choreographerPtr->scheduleVSync(); } void cancel() {} void setFrameRate(FrameRate) {} diff --git a/imagine/include/imagine/base/baseDefs.hh b/imagine/include/imagine/base/baseDefs.hh index 4c59c1a4e..3eb6a8730 100644 --- a/imagine/include/imagine/base/baseDefs.hh +++ b/imagine/include/imagine/base/baseDefs.hh @@ -248,6 +248,7 @@ struct WindowConfig; class ApplicationContext; class Application; struct ApplicationInitParams; +class FrameTimer; using WindowContainer = std::vector>; using ScreenContainer = std::vector>; diff --git a/imagine/include/imagine/base/iphone/IOSApplication.hh b/imagine/include/imagine/base/iphone/IOSApplication.hh index f93f24393..d27ea1bc2 100644 --- a/imagine/include/imagine/base/iphone/IOSApplication.hh +++ b/imagine/include/imagine/base/iphone/IOSApplication.hh @@ -26,8 +26,8 @@ class Window; struct ApplicationInitParams { void *uiAppPtr; - int argc; - char **argv; + int argc{}; + char **argv{}; constexpr CommandArgs commandArgs() const { diff --git a/imagine/include/imagine/base/iphone/IOSScreen.hh b/imagine/include/imagine/base/iphone/IOSScreen.hh index 822192352..e28e3db9f 100644 --- a/imagine/include/imagine/base/iphone/IOSScreen.hh +++ b/imagine/include/imagine/base/iphone/IOSScreen.hh @@ -18,6 +18,8 @@ #include #include #include +#include +#include #ifdef __OBJC__ #import @@ -29,6 +31,15 @@ namespace IG class ApplicationContext; +// TODO: add FrameTimer interface for CADisplayLink +using FrameTimerVariant = std::variant; + +class FrameTimer : public FrameTimerInterface +{ +public: + using FrameTimerInterface::FrameTimerInterface; +}; + class IOSScreen { public: diff --git a/imagine/include/imagine/base/x11/XApplication.hh b/imagine/include/imagine/base/x11/XApplication.hh index 1d5e4b1b3..15a0697fd 100644 --- a/imagine/include/imagine/base/x11/XApplication.hh +++ b/imagine/include/imagine/base/x11/XApplication.hh @@ -58,7 +58,7 @@ public: ~XApplication(); FDEventSource makeXDisplayConnection(EventLoop); ::_XDisplay *xDisplay() const; - FrameTimer makeFrameTimer(Screen &); + void emplaceFrameTimer(FrameTimer&, Screen&, bool useVariableTime = {}); void initPerWindowInputData(unsigned long xWin); void runX11Events(_XDisplay *); void runX11Events(); @@ -66,6 +66,7 @@ public: void setXdnd(unsigned long win, bool on); std::string inputKeyString(Input::Key rawKey, uint32_t modifiers) const; void setWindowCursor(unsigned long xWin, bool on); + SupportedFrameTimer supportedFrameTimerType() const { return supportedFrameTimer; } private: ::_XDisplay *dpy{}; diff --git a/imagine/make/clang.mk b/imagine/make/clang.mk index 2c5bf2ce6..3a8f71ea7 100755 --- a/imagine/make/clang.mk +++ b/imagine/make/clang.mk @@ -15,6 +15,9 @@ CFLAGS_CODEGEN += -fstrict-vtable-pointers # needed for CPPFLAGS += -D_LIBCPP_ENABLE_EXPERIMENTAL +# needed for DelegateFuncSet.hh +CXXFLAGS_WARN += -Wno-vla-extension + ifeq ($(LTO_MODE),lto) ltoMode := lto else ifeq ($(LTO_MODE),lto-fat) diff --git a/imagine/src/audio/coreaudio/coreaudio.cc b/imagine/src/audio/coreaudio/coreaudio.cc index adcf59754..d20aa4c30 100644 --- a/imagine/src/audio/coreaudio/coreaudio.cc +++ b/imagine/src/audio/coreaudio/coreaudio.cc @@ -37,6 +37,8 @@ CAOutputStream::CAOutputStream() .componentSubType = kAudioUnitSubType_DefaultOutput, #endif .componentManufacturer = kAudioUnitManufacturer_Apple, + .componentFlags{}, + .componentFlagsMask{} }; AudioComponent defaultOutput = AudioComponentFindNext(nullptr, &defaultOutputDescription); assert(defaultOutput); diff --git a/imagine/src/base/android/AndroidScreen.cc b/imagine/src/base/android/AndroidScreen.cc index bdd6210ed..7b23b69eb 100644 --- a/imagine/src/base/android/AndroidScreen.cc +++ b/imagine/src/base/android/AndroidScreen.cc @@ -111,8 +111,7 @@ void AndroidApplication::initScreens(JNIEnv *env, jobject baseActivity, jclass b jEnumDisplays(env, baseActivity, (jlong)nActivity); } -AndroidScreen::AndroidScreen(ApplicationContext ctx, InitParams params): - frameTimer{ctx.application().makeFrameTimer(*static_cast(this))} +AndroidScreen::AndroidScreen(ApplicationContext ctx, InitParams params) { auto [env, aDisplay, metrics, id, refreshRate, presentationDeadline, rotation] = params; assert(aDisplay); @@ -150,7 +149,7 @@ AndroidScreen::AndroidScreen(ApplicationContext ctx, InitParams params): else reliableFrameRate = false; } - frameTimer.setFrameRate(static_cast(this)->frameRate()); + ctx.application().emplaceFrameTimer(frameTimer, *static_cast(this)); updateSupportedFrameRates(ctx, env); // DisplayMetrics @@ -239,6 +238,13 @@ void Screen::unpostFrameTimer() frameTimer.cancel(); } +void Screen::setVariableFrameTime(bool useVariableTime) +{ + if(!shouldUpdateFrameTimer(frameTimer, useVariableTime)) + return; + application().emplaceFrameTimer(frameTimer, *static_cast(this), useVariableTime); +} + void Screen::setFrameInterval(int interval) { // TODO @@ -253,7 +259,7 @@ bool Screen::supportsFrameInterval() bool Screen::supportsTimestamps() const { - return !std::holds_alternative(frameTimer); + return appContext().androidSDK() >= 16; } void Screen::setFrameRate(FrameRate rate) diff --git a/imagine/src/base/android/AndroidWindow.cc b/imagine/src/base/android/AndroidWindow.cc index 3154a23dc..651b3c600 100644 --- a/imagine/src/base/android/AndroidWindow.cc +++ b/imagine/src/base/android/AndroidWindow.cc @@ -240,12 +240,8 @@ NativeWindow Window::nativeObject() const void Window::setIntendedFrameRate(FrameRate rate) { - if(appContext().androidSDK() < 30) - { - screen()->setFrameRate(rate); - return; - } - if(!nWin) [[unlikely]] + screen()->setFrameRate(rate); + if(appContext().androidSDK() < 30 || !nWin) return; if(!ANativeWindow_setFrameRate) [[unlikely]] { diff --git a/imagine/src/base/android/FrameTimer.cc b/imagine/src/base/android/FrameTimer.cc index fde068380..f7b89404b 100644 --- a/imagine/src/base/android/FrameTimer.cc +++ b/imagine/src/base/android/FrameTimer.cc @@ -31,22 +31,29 @@ namespace IG { -FrameTimer AndroidApplication::makeFrameTimer(Screen &screen) +void AndroidApplication::emplaceFrameTimer(FrameTimer &t, Screen &screen, bool useVariableTime) { - return visit(overloaded + if(useVariableTime) { - [&](JavaChoreographer &c) - { - return FrameTimer{std::in_place_type, c}; - }, - [&](NativeChoreographer &c) + t.emplace(screen); + } + else + { + return visit(overloaded { - if(c) - return FrameTimer{std::in_place_type, c}; - else // no choreographer - return FrameTimer{std::in_place_type, screen}; - }, - }, choreographer); + [&](JavaChoreographer &c) + { + t.emplace(c); + }, + [&](NativeChoreographer &c) + { + if(c) + t.emplace(c); + else // no choreographer + t.emplace(screen); + }, + }, choreographer); + } } void AndroidApplication::initChoreographer(JNIEnv *env, jobject baseActivity, jclass baseActivityClass, int32_t androidSDK) diff --git a/imagine/src/base/common/Screen.cc b/imagine/src/base/common/Screen.cc index a04cef8e9..d5f0edaf5 100644 --- a/imagine/src/base/common/Screen.cc +++ b/imagine/src/base/common/Screen.cc @@ -138,6 +138,12 @@ void Screen::unpostFrame() unpostFrameTimer(); } +bool Screen::shouldUpdateFrameTimer(const FrameTimer& frameTimer, bool newVariableFrameTimeValue) +{ + return (newVariableFrameTimeValue && !std::holds_alternative(frameTimer)) || + (!newVariableFrameTimeValue && std::holds_alternative(frameTimer)); +} + [[gnu::weak]] SteadyClockTime Screen::presentationDeadline() const { return {}; } diff --git a/imagine/src/base/common/SimpleFrameTimer.cc b/imagine/src/base/common/SimpleFrameTimer.cc index 53ee44b05..50a2feef6 100644 --- a/imagine/src/base/common/SimpleFrameTimer.cc +++ b/imagine/src/base/common/SimpleFrameTimer.cc @@ -48,6 +48,7 @@ SimpleFrameTimer::SimpleFrameTimer(Screen &screen, EventLoop loop): return true; } }, + interval{fromHz(screen.frameRate())}, eventLoop{loop} {} void SimpleFrameTimer::scheduleVSync() diff --git a/imagine/src/base/common/Window.cc b/imagine/src/base/common/Window.cc index 38e867608..1d1b802d8 100644 --- a/imagine/src/base/common/Window.cc +++ b/imagine/src/base/common/Window.cc @@ -66,15 +66,15 @@ void BaseWindow::attachDrawEvent() }); } -FrameTimeSource Window::evalFrameTimeSource(FrameTimeSource clock) const +FrameTimeSource Window::evalFrameTimeSource(FrameTimeSource src) const { - return clock == FrameTimeSource::Unset ? defaultFrameTimeSource() : clock; + return src == FrameTimeSource::Unset ? defaultFrameTimeSource() : src; } -bool Window::addOnFrame(OnFrameDelegate del, FrameTimeSource clock, int priority) +bool Window::addOnFrame(OnFrameDelegate del, FrameTimeSource src, int priority) { - clock = evalFrameTimeSource(clock); - if(clock == FrameTimeSource::Screen) + src = evalFrameTimeSource(src); + if(src != FrameTimeSource::Renderer) { return screen()->addOnFrame(del); } @@ -91,10 +91,10 @@ bool Window::addOnFrame(OnFrameDelegate del, FrameTimeSource clock, int priority } } -bool Window::removeOnFrame(OnFrameDelegate del, FrameTimeSource clock) +bool Window::removeOnFrame(OnFrameDelegate del, FrameTimeSource src) { - clock = evalFrameTimeSource(clock); - if(clock == FrameTimeSource::Screen) + src = evalFrameTimeSource(src); + if(src != FrameTimeSource::Renderer) { return screen()->removeOnFrame(del); } @@ -112,7 +112,18 @@ bool Window::moveOnFrame(Window &srcWin, OnFrameDelegate del, FrameTimeSource sr FrameTimeSource Window::defaultFrameTimeSource() const { - return screen()->supportsTimestamps() ? FrameTimeSource::Screen : FrameTimeSource::Renderer; + return screen()->supportsTimestamps() ? FrameTimeSource::Screen : + (Config::envIsAndroid ? FrameTimeSource::Renderer : FrameTimeSource::Timer); +} + +void Window::configureFrameTimeSource(FrameTimeSource src) +{ + src = evalFrameTimeSource(src); + log.info("configuring for frame time source:{}", wise_enum::to_string(src)); + if(src != FrameTimeSource::Renderer) + { + screen()->setVariableFrameTime(src == FrameTimeSource::Timer); + } } void Window::resetAppData() diff --git a/imagine/src/base/common/eventloop/CFEventLoop.cc b/imagine/src/base/common/eventloop/CFEventLoop.cc index 332722c26..afc762305 100644 --- a/imagine/src/base/common/eventloop/CFEventLoop.cc +++ b/imagine/src/base/common/eventloop/CFEventLoop.cc @@ -60,7 +60,7 @@ CFFDEventSource::CFFDEventSource(const char *debugLabel, MaybeUniqueFileDescript debugLabel{debugLabel ? debugLabel : "unnamed"}, info{std::make_unique()} { - CFFileDescriptorContext ctx{.info = info.get()}; + CFFileDescriptorContext ctx{.version{}, .info = info.get(), .retain{}, .release{}, .copyDescription{}}; info->fdRef = CFFileDescriptorCreate(kCFAllocatorDefault, fd.release(), fd.ownsFd(), eventCallback, &ctx); } diff --git a/imagine/src/base/x11/FrameTimer.cc b/imagine/src/base/x11/FrameTimer.cc index 386cdc6d9..1196b1a3f 100644 --- a/imagine/src/base/x11/FrameTimer.cc +++ b/imagine/src/base/x11/FrameTimer.cc @@ -21,15 +21,22 @@ namespace IG { -FrameTimer XApplication::makeFrameTimer(Screen &screen) +void XApplication::emplaceFrameTimer(FrameTimer &t, Screen &screen, bool useVariableTime) { - switch(supportedFrameTimer) + if(useVariableTime) { - default: return FrameTimer{std::in_place_type, screen}; - #if CONFIG_PACKAGE_LIBDRM - case SupportedFrameTimer::DRM: return FrameTimer{std::in_place_type, screen}; - #endif - case SupportedFrameTimer::FBDEV: return FrameTimer{std::in_place_type, screen}; + t.emplace(screen); + } + else + { + switch(supportedFrameTimer) + { + default: t.emplace(screen); break; + #if CONFIG_PACKAGE_LIBDRM + case SupportedFrameTimer::DRM: t.emplace(screen); break; + #endif + case SupportedFrameTimer::FBDEV: t.emplace(screen); break; + } } } diff --git a/imagine/src/base/x11/XScreen.cc b/imagine/src/base/x11/XScreen.cc index 8459bdb10..5fbfc90d1 100644 --- a/imagine/src/base/x11/XScreen.cc +++ b/imagine/src/base/x11/XScreen.cc @@ -26,8 +26,7 @@ namespace IG { -XScreen::XScreen(ApplicationContext ctx, InitParams params): - frameTimer{ctx.application().makeFrameTimer(*static_cast(this))} +XScreen::XScreen(ApplicationContext ctx, InitParams params) { auto *xScreen = (::Screen*)params.xScreen; assert(xScreen); @@ -78,7 +77,7 @@ XScreen::XScreen(ApplicationContext ctx, InitParams params): } logMsg("screen:%p %dx%d (%dx%dmm) %.2fHz", xScreen, WidthOfScreen(xScreen), HeightOfScreen(xScreen), (int)xMM, (int)yMM, frameRate_); - frameTimer.setFrameRate(frameRate_); + ctx.application().emplaceFrameTimer(frameTimer, *static_cast(this)); } void *XScreen::nativeObject() const @@ -173,7 +172,7 @@ bool Screen::supportsFrameInterval() bool Screen::supportsTimestamps() const { - return !std::holds_alternative(frameTimer); + return application().supportedFrameTimerType() != SupportedFrameTimer::SIMPLE; } std::span Screen::supportedFrameRates() const @@ -182,4 +181,11 @@ std::span Screen::supportedFrameRates() const return {&frameRate_, 1}; } +void Screen::setVariableFrameTime(bool useVariableTime) +{ + if(!shouldUpdateFrameTimer(frameTimer, useVariableTime)) + return; + application().emplaceFrameTimer(frameTimer, *static_cast(this), useVariableTime); +} + }