diff --git a/CMakeLists.txt b/CMakeLists.txt index 823736a..aeed832 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -543,7 +543,7 @@ else() set_source_files_properties(source/conversions.cpp PROPERTIES COMPILE_OPTIONS "$<$:-O3;-funroll-loops>") endif() -set(EXECUTABLE_SOURCE_FILES source/cc3dsfs.cpp source/utils.cpp source/audio_data.cpp source/audio.cpp source/frontend.cpp source/TextRectangle.cpp source/TextRectanglePool.cpp source/WindowScreen.cpp source/WindowScreen_Menu.cpp source/devicecapture.cpp source/conversions.cpp source/ExtraButtons.cpp source/Menus/ConnectionMenu.cpp source/Menus/OptionSelectionMenu.cpp source/Menus/MainMenu.cpp source/Menus/VideoMenu.cpp source/Menus/CropMenu.cpp source/Menus/PARMenu.cpp source/Menus/RotationMenu.cpp source/Menus/OffsetMenu.cpp source/Menus/AudioMenu.cpp source/Menus/BFIMenu.cpp source/Menus/RelativePositionMenu.cpp source/Menus/ResolutionMenu.cpp source/Menus/FileConfigMenu.cpp source/Menus/ExtraSettingsMenu.cpp source/Menus/StatusMenu.cpp source/Menus/LicenseMenu.cpp source/WindowCommands.cpp source/Menus/ShortcutMenu.cpp source/Menus/ActionSelectionMenu.cpp source/Menus/ScalingRatioMenu.cpp source/Menus/ISNitroMenu.cpp source/Menus/PartnerCTRMenu.cpp source/Menus/VideoEffectsMenu.cpp source/CaptureDataBuffers.cpp source/Menus/InputMenu.cpp source/Menus/AudioDeviceMenu.cpp source/Menus/SeparatorMenu.cpp source/Menus/ColorCorrectionMenu.cpp source/Menus/Main3DMenu.cpp source/Menus/SecondScreen3DRelativePositionMenu.cpp source/Menus/USBConflictResolutionMenu.cpp source/Menus/Optimize3DSMenu.cpp source/Menus/Optimize3DSDowngradeV2Menu.cpp source/Menus/OptimizeSerialKeyAddMenu.cpp source/Menus/OptimizeOldFWConfigMenu.cpp source/libgpiod_compat.cpp ${TOOLS_DATA_DIR}/optimize_serial_key_add_table.cpp ${TOOLS_DATA_DIR}/optimize_serial_key_next_char_table.cpp ${TOOLS_DATA_DIR}/optimize_serial_key_prev_char_table.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp ${TOOLS_DATA_DIR}/font_mono_ttf.cpp ${TOOLS_DATA_DIR}/shaders_list.cpp ${SOURCE_CPP_EXTRA_FILES}) +set(EXECUTABLE_SOURCE_FILES source/cc3dsfs.cpp source/utils.cpp source/audio_data.cpp source/audio.cpp source/frontend.cpp source/TextRectangle.cpp source/TextRectanglePool.cpp source/WindowScreen.cpp source/WindowScreen_Menu.cpp source/devicecapture.cpp source/conversions.cpp source/ExtraButtons.cpp source/Menus/ConnectionMenu.cpp source/Menus/OptionSelectionMenu.cpp source/Menus/MainMenu.cpp source/Menus/VideoMenu.cpp source/Menus/CropMenu.cpp source/Menus/PARMenu.cpp source/Menus/RotationMenu.cpp source/Menus/OffsetMenu.cpp source/Menus/AudioMenu.cpp source/Menus/BFIMenu.cpp source/Menus/RelativePositionMenu.cpp source/Menus/ResolutionMenu.cpp source/Menus/FileConfigMenu.cpp source/Menus/ExtraSettingsMenu.cpp source/Menus/StatusMenu.cpp source/Menus/LicenseMenu.cpp source/WindowCommands.cpp source/Menus/ShortcutMenu.cpp source/Menus/ActionSelectionMenu.cpp source/Menus/ScalingRatioMenu.cpp source/Menus/ISNitroMenu.cpp source/Menus/PartnerCTRMenu.cpp source/Menus/VideoEffectsMenu.cpp source/CaptureDataBuffers.cpp source/Menus/InputMenu.cpp source/Menus/AudioDeviceMenu.cpp source/Menus/SeparatorMenu.cpp source/Menus/ColorCorrectionMenu.cpp source/Menus/Main3DMenu.cpp source/Menus/SecondScreen3DRelativePositionMenu.cpp source/Menus/USBConflictResolutionMenu.cpp source/Menus/Optimize3DSMenu.cpp source/Menus/Optimize3DSDowngradeV2Menu.cpp source/Menus/OptimizeSerialKeyAddMenu.cpp source/Menus/OptimizeOldFWConfigMenu.cpp source/Menus/OutputFramerateMenu.cpp source/libgpiod_compat.cpp ${TOOLS_DATA_DIR}/optimize_serial_key_add_table.cpp ${TOOLS_DATA_DIR}/optimize_serial_key_next_char_table.cpp ${TOOLS_DATA_DIR}/optimize_serial_key_prev_char_table.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp ${TOOLS_DATA_DIR}/font_mono_ttf.cpp ${TOOLS_DATA_DIR}/shaders_list.cpp ${SOURCE_CPP_EXTRA_FILES}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Android") add_compile_flag("SFML_SYSTEM_ANDROID") diff --git a/README.md b/README.md index a28e243..c6ca95d 100755 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ The CC3DSFS\_CFG\_DIR environment variable can be used to specify a different ta - Current font in use: OFL Sorts Mill Goudy TT - Enabling Slow Poll may slightly boost the FPS of the software, at the cost of an extremely slight decrease in frame latency, and slower reaction times of the software to key presses. Disabled by default (as when the FPS are greater than the CC's, it's not reccomended). - On MacOS, you may get a notice about Apple being unable to check for malware, or the developer being unknown. To open the program regardless of that, check the [Official Guide for "Opening Applications from Unknown Developers" from Apple, for your version of MacOS](https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unknown-developer-mh40616/mac). +- Certain devices (including Apple hardware running MacOS 15 or newer) may have issues displaying images at the refresh rate of the Nintendo DS/3DS consoles. To account for this, there are options to setup a fixed framerate output. They can be found under _Video Settings_ -> _Output Framerate Settings_. When making use of this fixed output framerate, it is suggested to turn VSync on to avoid artifacts. For the best results, make sure that the fixed framerate is a multiple of 60, and that the fixed framerate matches the display's framerate. - Certain TVs/Monitors may add some audio delay for the purpose of video/lip syncing. If you're experiencing too much audio delay when using this software, try checking in the TV/Monitor settings whether you can reduce that added delay. One of the names used for that setting is "Lip Sync", or something along that line. - MacOS does not allow running multiple instances of the same application, normally. If you want to run multiple instances of cc3dsfs on MacOS, open the terminal in the folder where cc3dsfs is and type `open -n cc3dsfs.app`. - To reset to the default settings, besides the options shown in [Controls](#Controls), you can find the _Reset Settings_ option inside of the _Extra Settings_. diff --git a/include/Menus/OutputFramerateMenu.hpp b/include/Menus/OutputFramerateMenu.hpp new file mode 100644 index 0000000..b552ccf --- /dev/null +++ b/include/Menus/OutputFramerateMenu.hpp @@ -0,0 +1,44 @@ +#ifndef __OUTPUTFRAMERATEMENU_HPP +#define __OUTPUTFRAMERATEMENU_HPP + +#include "OptionSelectionMenu.hpp" +#include + +#include "TextRectangle.hpp" +#include "sfml_gfx_structs.hpp" +#include "display_structs.hpp" + +enum OutputFramerateMenuOutAction{ + OUTPUT_FRAMERATE_MENU_NO_ACTION, + OUTPUT_FRAMERATE_MENU_BACK, + OUTPUT_FRAMERATE_MENU_MODE_INC, + OUTPUT_FRAMERATE_MENU_MODE_DEC, + OUTPUT_FRAMERATE_MENU_RATE_001_INC, + OUTPUT_FRAMERATE_MENU_RATE_001_DEC, + OUTPUT_FRAMERATE_MENU_RATE_01_INC, + OUTPUT_FRAMERATE_MENU_RATE_01_DEC, + OUTPUT_FRAMERATE_MENU_RATE_1_INC, + OUTPUT_FRAMERATE_MENU_RATE_1_DEC, + OUTPUT_FRAMERATE_MENU_RATE_10_INC, + OUTPUT_FRAMERATE_MENU_RATE_10_DEC, +}; + +class OutputFramerateMenu : public OptionSelectionMenu { +public: + OutputFramerateMenu(TextRectanglePool* text_pool); + ~OutputFramerateMenu(); + void prepare(float scaling_factor, int view_size_x, int view_size_y, DisplayData* display_data); + void insert_data(); + OutputFramerateMenuOutAction selected_index = OutputFramerateMenuOutAction::OUTPUT_FRAMERATE_MENU_NO_ACTION; + void reset_output_option(); +protected: + bool is_option_inc_dec(int index); + void set_output_option(int index, int action); + size_t get_num_options(); + std::string get_string_option(int index, int action); + void class_setup(); +private: + int *options_indexes; + size_t num_enabled_options; +}; +#endif diff --git a/include/Menus/VideoMenu.hpp b/include/Menus/VideoMenu.hpp index b99d6b0..66bdfc6 100755 --- a/include/Menus/VideoMenu.hpp +++ b/include/Menus/VideoMenu.hpp @@ -13,6 +13,7 @@ enum VideoMenuOutAction{ VIDEO_MENU_BACK, VIDEO_MENU_VSYNC, VIDEO_MENU_ASYNC, + VIDEO_MENU_OUTPUT_FRAMERATE_SETTINGS, VIDEO_MENU_BLUR, VIDEO_MENU_PADDING, VIDEO_MENU_CROPPING, diff --git a/include/display_structs.hpp b/include/display_structs.hpp index 5a67209..751b8b8 100755 --- a/include/display_structs.hpp +++ b/include/display_structs.hpp @@ -26,7 +26,7 @@ enum ScreenType { TOP, BOTTOM, JOINT }; enum BottomRelativePosition { UNDER_TOP, LEFT_TOP, ABOVE_TOP, RIGHT_TOP, BOT_REL_POS_END }; enum SecondScreen3DRelativePosition { UNDER_FIRST, LEFT_FIRST, ABOVE_FIRST, RIGHT_FIRST, SECOND_SCREEN_3D_REL_POS_END }; enum NonIntegerScalingModes { SMALLER_PRIORITY, INVERSE_PROPORTIONAL_PRIORITY, EQUAL_PRIORITY, PROPORTIONAL_PRIORITY, BIGGER_PRIORITY, END_NONINT_SCALE_MODES }; -enum CurrMenuType { DEFAULT_MENU_TYPE, CONNECT_MENU_TYPE, MAIN_MENU_TYPE, VIDEO_MENU_TYPE, AUDIO_MENU_TYPE, CROP_MENU_TYPE, TOP_PAR_MENU_TYPE, BOTTOM_PAR_MENU_TYPE, ROTATION_MENU_TYPE, OFFSET_MENU_TYPE, BFI_MENU_TYPE, LOAD_MENU_TYPE, SAVE_MENU_TYPE, RESOLUTION_MENU_TYPE, EXTRA_MENU_TYPE, STATUS_MENU_TYPE, LICENSES_MENU_TYPE, RELATIVE_POS_MENU_TYPE, SHORTCUTS_MENU_TYPE, ACTION_SELECTION_MENU_TYPE, SCALING_RATIO_MENU_TYPE, ISN_MENU_TYPE, VIDEO_EFFECTS_MENU_TYPE, INPUT_MENU_TYPE, AUDIO_DEVICE_MENU_TYPE, SEPARATOR_MENU_TYPE, COLOR_CORRECTION_MENU_TYPE, MAIN_3D_MENU_TYPE, SECOND_SCREEN_RELATIVE_POS_MENU_TYPE, USB_CONFLICT_RESOLUTION_MENU_TYPE, OPTIMIZE_3DS_MENU_TYPE, OPTIMIZE_SERIAL_KEY_ADD_MENU_TYPE, OPTIMIZE_OLD_FW_CONFIG_MENU, OPTIMIZE_3DS_DOWNGRADEV2_MENU_TYPE, RECONNECT_MENU_TYPE, PARTNER_CTR_MENU_TYPE }; +enum CurrMenuType { DEFAULT_MENU_TYPE, CONNECT_MENU_TYPE, MAIN_MENU_TYPE, VIDEO_MENU_TYPE, OUTPUT_FRAMERATE_MENU_TYPE, AUDIO_MENU_TYPE, CROP_MENU_TYPE, TOP_PAR_MENU_TYPE, BOTTOM_PAR_MENU_TYPE, ROTATION_MENU_TYPE, OFFSET_MENU_TYPE, BFI_MENU_TYPE, LOAD_MENU_TYPE, SAVE_MENU_TYPE, RESOLUTION_MENU_TYPE, EXTRA_MENU_TYPE, STATUS_MENU_TYPE, LICENSES_MENU_TYPE, RELATIVE_POS_MENU_TYPE, SHORTCUTS_MENU_TYPE, ACTION_SELECTION_MENU_TYPE, SCALING_RATIO_MENU_TYPE, ISN_MENU_TYPE, VIDEO_EFFECTS_MENU_TYPE, INPUT_MENU_TYPE, AUDIO_DEVICE_MENU_TYPE, SEPARATOR_MENU_TYPE, COLOR_CORRECTION_MENU_TYPE, MAIN_3D_MENU_TYPE, SECOND_SCREEN_RELATIVE_POS_MENU_TYPE, USB_CONFLICT_RESOLUTION_MENU_TYPE, OPTIMIZE_3DS_MENU_TYPE, OPTIMIZE_SERIAL_KEY_ADD_MENU_TYPE, OPTIMIZE_OLD_FW_CONFIG_MENU, OPTIMIZE_3DS_DOWNGRADEV2_MENU_TYPE, RECONNECT_MENU_TYPE, PARTNER_CTR_MENU_TYPE }; enum InputColorspaceMode { FULL_COLORSPACE, DS_COLORSPACE, GBA_COLORSPACE, INPUT_COLORSPACE_END }; enum FrameBlendingMode { NO_FRAME_BLENDING, FULL_FRAME_BLENDING, DS_3D_BOTH_SCREENS_FRAME_BLENDING, FRAME_BLENDING_END }; @@ -92,6 +92,8 @@ struct DisplayData { bool interleaved_3d; bool force_disable_mouse; bool do_ratio_cycling; + bool fixed_output_framerate; + double target_fixed_output_framerate; }; struct ExtraButtonShortcuts { diff --git a/include/frontend.hpp b/include/frontend.hpp index 52b0838..218b2f2 100755 --- a/include/frontend.hpp +++ b/include/frontend.hpp @@ -45,6 +45,7 @@ #include "OptimizeSerialKeyAddMenu.hpp" #include "OptimizeOldFWConfigMenu.hpp" #include "Optimize3DSDowngradeV2Menu.hpp" +#include "OutputFramerateMenu.hpp" #include "display_structs.hpp" #include "event_structs.hpp" #include "shaders_list.hpp" @@ -216,6 +217,7 @@ private: OptimizeSerialKeyAddMenu* optimize_serial_key_add_menu; OptimizeOldFWConfigMenu* optimize_old_fw_config_menu; Optimize3DSDowngradeV2Menu* optimize_3ds_downgradev2_menu; + OutputFramerateMenu* output_framerate_menu; std::vector possible_crops; std::vector possible_crops_ds; @@ -442,6 +444,7 @@ private: void setup_optimize_serial_key_add_menu(bool reset_data = true); void setup_optimize_old_fw_config_menu(bool reset_data = true); void setup_optimize_n3ds_downgradev2_menu(bool reset_data = true); + void setup_output_framerate_menu(bool reset_data = true); void update_connection(); bool has_menu_textbox(); }; @@ -471,6 +474,7 @@ void reset_input_data(InputData* input_data); void reset_shared_data(SharedData* shared_data); void reset_fullscreen_info(ScreenInfo &info); +void sanitize_display_data(DisplayData* display_data); void sanitize_enabled_info(ScreenInfo &top_bot_info, ScreenInfo &top_info, ScreenInfo &bot_info); void override_set_data_to_screen_info(override_win_data &override_win, ScreenInfo &info); void reset_screen_info(ScreenInfo &info); @@ -494,6 +498,7 @@ std::string get_name_non_int_mode(NonIntegerScalingModes input); std::string get_name_frame_blending_mode(FrameBlendingMode input); std::string get_name_input_colorspace_mode(InputColorspaceMode input); bool is_input_data_valid(InputData* input_data, bool consider_buttons); -void default_sleep(float wanted_ms = -1); +void default_sleep(double wanted_ms = -1); +void precise_sleep(double wanted_ms = -1, double divisor = 8, double spin_threshold_ms = 0.5); #endif diff --git a/include/utils.hpp b/include/utils.hpp index 6f70155..94c2767 100755 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -49,10 +49,13 @@ #define SIMPLE_RESET_DATA_INDEX -2 #define CREATE_NEW_FILE_INDEX -3 +typedef uint64_t version_t; + #ifdef ANDROID_COMPILATION ANativeActivity* getAndroidNativeActivity(); #endif std::string get_version_string(bool get_letter = true); +version_t get_version_string_number(std::string version_string); void ActualConsoleOutTextError(std::string out_string); void ActualConsoleOutText(std::string out_string); std::string get_base_path(bool for_presets, bool do_existence_check=true); diff --git a/source/Menus/OutputFramerateMenu.cpp b/source/Menus/OutputFramerateMenu.cpp new file mode 100644 index 0000000..cdae88b --- /dev/null +++ b/source/Menus/OutputFramerateMenu.cpp @@ -0,0 +1,140 @@ +#include "OutputFramerateMenu.hpp" + +#define NUM_TOTAL_MENU_OPTIONS (sizeof(pollable_options)/sizeof(pollable_options[0])) + +struct OutputFramerateMenuOptionInfo { + const std::string base_name; + const bool is_inc; + const std::string dec_str; + const std::string inc_str; + const OutputFramerateMenuOutAction inc_out_action; + const OutputFramerateMenuOutAction out_action; +}; + +static const OutputFramerateMenuOptionInfo output_rate_mode_change_option = { +.base_name = "Framerate", +.is_inc = true, .dec_str = "<", .inc_str = ">", .inc_out_action = OUTPUT_FRAMERATE_MENU_MODE_INC, +.out_action = OUTPUT_FRAMERATE_MENU_MODE_DEC}; + +static const OutputFramerateMenuOptionInfo output_rate_fixed_change_10_option = { +.base_name = "", +.is_inc = true, .dec_str = "-10", .inc_str = "+10", .inc_out_action = OUTPUT_FRAMERATE_MENU_RATE_10_INC, +.out_action = OUTPUT_FRAMERATE_MENU_RATE_10_DEC}; + +static const OutputFramerateMenuOptionInfo output_rate_fixed_change_1_option = { +.base_name = "Fixed Out Framerate:", +.is_inc = true, .dec_str = "-1", .inc_str = "+1", .inc_out_action = OUTPUT_FRAMERATE_MENU_RATE_1_INC, +.out_action = OUTPUT_FRAMERATE_MENU_RATE_1_DEC}; + +static const OutputFramerateMenuOptionInfo output_rate_fixed_change_01_option = { +.base_name = "", +.is_inc = true, .dec_str = "-0.1", .inc_str = "+0.1", .inc_out_action = OUTPUT_FRAMERATE_MENU_RATE_01_INC, +.out_action = OUTPUT_FRAMERATE_MENU_RATE_01_DEC}; + +static const OutputFramerateMenuOptionInfo output_rate_fixed_change_001_option = { +.base_name = "", +.is_inc = true, .dec_str = "-0.01", .inc_str = "+0.01", .inc_out_action = OUTPUT_FRAMERATE_MENU_RATE_001_INC, +.out_action = OUTPUT_FRAMERATE_MENU_RATE_001_DEC}; + +// Wondering if maybe a version with a textfield would be better... +static const OutputFramerateMenuOptionInfo* pollable_options[] = { +&output_rate_mode_change_option, +&output_rate_fixed_change_10_option, +&output_rate_fixed_change_1_option, +&output_rate_fixed_change_01_option, +&output_rate_fixed_change_001_option, +}; + +OutputFramerateMenu::OutputFramerateMenu(TextRectanglePool* text_rectangle_pool) : OptionSelectionMenu(){ + this->options_indexes = new int[NUM_TOTAL_MENU_OPTIONS]; + this->initialize(text_rectangle_pool); + this->num_enabled_options = 0; +} + +OutputFramerateMenu::~OutputFramerateMenu() { + delete []this->options_indexes; +} + +void OutputFramerateMenu::class_setup() { + this->num_options_per_screen = 5; + this->min_elements_text_scaling_factor = num_options_per_screen + 2; + this->width_factor_menu = 16; + this->width_divisor_menu = 9; + this->base_height_factor_menu = 12; + this->base_height_divisor_menu = 6; + this->min_text_size = 0.3f; + this->max_width_slack = 1.1f; + this->menu_color = sf::Color(30, 30, 60, 192); + this->title = "Out Framerate Settings"; + this->show_back_x = true; + this->show_x = false; + this->show_title = true; +} + +void OutputFramerateMenu::insert_data() { + this->num_enabled_options = 0; + for(size_t i = 0; i < NUM_TOTAL_MENU_OPTIONS; i++) { + this->options_indexes[this->num_enabled_options] = (int)i; + this->num_enabled_options++; + } + this->prepare_options(); +} + +void OutputFramerateMenu::reset_output_option() { + this->selected_index = OutputFramerateMenuOutAction::OUTPUT_FRAMERATE_MENU_NO_ACTION; +} + +void OutputFramerateMenu::set_output_option(int index, int action) { + if(index == BACK_X_OUTPUT_OPTION) + this->selected_index = OUTPUT_FRAMERATE_MENU_BACK; + else if((action == INC_ACTION) && this->is_option_inc_dec(index)) + this->selected_index = pollable_options[this->options_indexes[index]]->inc_out_action; + else + this->selected_index = pollable_options[this->options_indexes[index]]->out_action; +} + +size_t OutputFramerateMenu::get_num_options() { + return this->num_enabled_options; +} + +std::string OutputFramerateMenu::get_string_option(int index, int action) { + if((action == INC_ACTION) && this->is_option_inc_dec(index)) + return pollable_options[this->options_indexes[index]]->inc_str; + if((action == DEC_ACTION) && this->is_option_inc_dec(index)) + return pollable_options[this->options_indexes[index]]->dec_str; + return pollable_options[this->options_indexes[index]]->base_name; +} + +bool OutputFramerateMenu::is_option_inc_dec(int index) { + return pollable_options[this->options_indexes[index]]->is_inc; +} + +static std::string get_output_framerate_mode(DisplayData* display_data) { + return display_data->fixed_output_framerate ? "Fixed" : "Console"; +} + +void OutputFramerateMenu::prepare(float menu_scaling_factor, int view_size_x, int view_size_y, DisplayData *display_data) { + int num_pages = this->get_num_pages(); + if(this->future_data.page >= num_pages) + this->future_data.page = num_pages - 1; + int start = this->future_data.page * this->num_options_per_screen; + for(int i = 0; i < this->num_options_per_screen + 1; i++) { + int index = (i * this->single_option_multiplier) + this->elements_start_id; + if(!this->future_enabled_labels[index]) + continue; + int real_index = start + i; + int option_index = this->options_indexes[real_index]; + switch(pollable_options[option_index]->out_action) { + case OUTPUT_FRAMERATE_MENU_MODE_DEC: + this->labels[index]->setText(this->setTextOptionString(real_index, get_output_framerate_mode(display_data))); + break; + case OUTPUT_FRAMERATE_MENU_RATE_01_DEC: + this->labels[index]->setText(get_float_str_decimals((float)display_data->target_fixed_output_framerate, 2)); + break; + default: + break; + } + } + + this->base_prepare(menu_scaling_factor, view_size_x, view_size_y); +} diff --git a/source/Menus/VideoMenu.cpp b/source/Menus/VideoMenu.cpp index 4ba71ec..2c16847 100755 --- a/source/Menus/VideoMenu.cpp +++ b/source/Menus/VideoMenu.cpp @@ -32,6 +32,13 @@ static const VideoMenuOptionInfo async_option = { .is_inc = false, .dec_str = "", .inc_str = "", .inc_out_action = VIDEO_MENU_NO_ACTION, .out_action = VIDEO_MENU_ASYNC}; +static const VideoMenuOptionInfo framerate_settings_option = { +.base_name = "Output Framerate Settings", .false_name = "", +.active_fullscreen = true, .active_windowed_screen = true, .requires_titlebar_possible = false, +.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, +.is_inc = false, .dec_str = "", .inc_str = "", .inc_out_action = VIDEO_MENU_NO_ACTION, +.out_action = VIDEO_MENU_OUTPUT_FRAMERATE_SETTINGS}; + static const VideoMenuOptionInfo blur_option = { .base_name = "Turn Blur Off", .false_name = "Turn Blur On", .active_fullscreen = true, .active_windowed_screen = true, .requires_titlebar_possible = false, @@ -213,6 +220,7 @@ static const VideoMenuOptionInfo* pollable_options[] = { &separator_settings_option, &vsync_option, &async_option, +&framerate_settings_option, &blur_option, //&fast_poll_option, &small_screen_offset_option, diff --git a/source/WindowScreen_Menu.cpp b/source/WindowScreen_Menu.cpp index 9eda881..aa47703 100755 --- a/source/WindowScreen_Menu.cpp +++ b/source/WindowScreen_Menu.cpp @@ -96,6 +96,11 @@ static double FPSArrayGetAverage(FPSArray *array) { return fps_sum / available_fps; } +static void change_display_data_fixed_framerate(DisplayData* display_data, double change) { + display_data->target_fixed_output_framerate += change; + sanitize_display_data(display_data); +} + void WindowScreen::init_menus() { for(int i = 0; i < NUM_FINGERS_SUPPORTED; i++) this->is_touch_currently_active[i] = false; @@ -135,6 +140,7 @@ void WindowScreen::init_menus() { this->optimize_serial_key_add_menu = new OptimizeSerialKeyAddMenu(this->text_rectangle_pool); this->optimize_old_fw_config_menu = new OptimizeOldFWConfigMenu(this->text_rectangle_pool); this->optimize_3ds_downgradev2_menu = new Optimize3DSDowngradeV2Menu(this->text_rectangle_pool); + this->output_framerate_menu = new OutputFramerateMenu(this->text_rectangle_pool); } void WindowScreen::destroy_menus() { @@ -170,6 +176,7 @@ void WindowScreen::destroy_menus() { delete this->optimize_serial_key_add_menu; delete this->optimize_old_fw_config_menu; delete this->optimize_3ds_downgradev2_menu; + delete this->output_framerate_menu; } void WindowScreen::set_close(int ret_val) { @@ -1330,6 +1337,15 @@ void WindowScreen::setup_optimize_n3ds_downgradev2_menu(bool reset_data) { } } +void WindowScreen::setup_output_framerate_menu(bool reset_data) { + if(!this->can_setup_menu()) + return; + if(this->curr_menu != OUTPUT_FRAMERATE_MENU_TYPE) { + this->switch_to_menu(OUTPUT_FRAMERATE_MENU_TYPE, this->output_framerate_menu, reset_data); + this->output_framerate_menu->insert_data(); + } +} + void WindowScreen::update_save_menu() { if(this->curr_menu == SAVE_MENU_TYPE) { this->curr_menu = DEFAULT_MENU_TYPE; @@ -1889,6 +1905,9 @@ void WindowScreen::poll(bool do_everything) { case VIDEO_MENU_3D_SETTINGS: this->setup_main_3d_menu(); break; + case VIDEO_MENU_OUTPUT_FRAMERATE_SETTINGS: + this->setup_output_framerate_menu(); + break; default: break; } @@ -2590,6 +2609,47 @@ void WindowScreen::poll(bool do_everything) { } this->loaded_menu_ptr->reset_output_option(); break; + case OUTPUT_FRAMERATE_MENU_TYPE: + switch(this->output_framerate_menu->selected_index) { + case OUTPUT_FRAMERATE_MENU_BACK: + this->setup_video_menu(false); + done = true; + break; + case OUTPUT_FRAMERATE_MENU_NO_ACTION: + break; + case OUTPUT_FRAMERATE_MENU_MODE_INC: + case OUTPUT_FRAMERATE_MENU_MODE_DEC: + this->display_data->fixed_output_framerate = !this->display_data->fixed_output_framerate; + break; + case OUTPUT_FRAMERATE_MENU_RATE_001_INC: + change_display_data_fixed_framerate(this->display_data, 0.01); + break; + case OUTPUT_FRAMERATE_MENU_RATE_001_DEC: + change_display_data_fixed_framerate(this->display_data, -0.01); + break; + case OUTPUT_FRAMERATE_MENU_RATE_01_INC: + change_display_data_fixed_framerate(this->display_data, 0.1); + break; + case OUTPUT_FRAMERATE_MENU_RATE_01_DEC: + change_display_data_fixed_framerate(this->display_data, -0.1); + break; + case OUTPUT_FRAMERATE_MENU_RATE_1_INC: + change_display_data_fixed_framerate(this->display_data, 1); + break; + case OUTPUT_FRAMERATE_MENU_RATE_1_DEC: + change_display_data_fixed_framerate(this->display_data, -1); + break; + case OUTPUT_FRAMERATE_MENU_RATE_10_INC: + change_display_data_fixed_framerate(this->display_data, 10); + break; + case OUTPUT_FRAMERATE_MENU_RATE_10_DEC: + change_display_data_fixed_framerate(this->display_data, -10); + break; + default: + break; + } + this->loaded_menu_ptr->reset_output_option(); + break; default: break; } @@ -2926,6 +2986,9 @@ void WindowScreen::prepare_menu_draws(int view_size_x, int view_size_y) { case OPTIMIZE_3DS_DOWNGRADEV2_MENU_TYPE: this->optimize_3ds_downgradev2_menu->prepare(menu_scaling_factor, view_size_x, view_size_y); break; + case OUTPUT_FRAMERATE_MENU_TYPE: + this->output_framerate_menu->prepare(menu_scaling_factor, view_size_x, view_size_y, this->display_data); + break; default: break; } diff --git a/source/cc3dsfs.cpp b/source/cc3dsfs.cpp index afd76b5..403d741 100755 --- a/source/cc3dsfs.cpp +++ b/source/cc3dsfs.cpp @@ -62,6 +62,19 @@ static void SuccessConnectionOutTextGenerator(OutTextData &out_text_data, Captur } } +static void apply_version_changes_to_data(std::string version_loaded, ScreenInfo &top_info, ScreenInfo &bottom_info, ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, CaptureStatus* capture_status) { + version_t loaded_ver_id = get_version_string_number(version_loaded); + // Apply patches here... + if(loaded_ver_id < get_version_string_number("1.3.0.2")) { + #if (defined(__APPLE__)) + // Should this be the default...? + //top_info.v_sync_enabled = true; + //bottom_info.v_sync_enabled = true; + //joint_info.v_sync_enabled = true; + #endif + } +} + static bool load_shared(const std::string path, const std::string name, SharedData* shared_data, OutTextData &out_text_data, bool do_print) { std::ifstream file(path + name); std::string line; @@ -138,6 +151,7 @@ static bool load_shared(const std::string path, const std::string name, SharedDa } static bool load(const std::string path, const std::string name, ScreenInfo &top_info, ScreenInfo &bottom_info, ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, OutTextData &out_text_data, CaptureStatus* capture_status) { + std::string version_loaded = ""; std::ifstream file(path + name); std::string line; bool loaded_3d_request = false; @@ -168,6 +182,11 @@ static bool load(const std::string path, const std::string name, ScreenInfo &top if(load_screen_info(key, value, "top_", top_info)) continue; + if(key == "version") { + version_loaded = value; + continue; + } + if(key == "split") { bool split = std::stoi(value); joint_info.window_enabled = !split; @@ -192,6 +211,16 @@ static bool load(const std::string path, const std::string name, ScreenInfo &top continue; } + if(key == "fixed_output_framerate") { + display_data.fixed_output_framerate = std::stoi(value); + continue; + } + + if(key == "target_fixed_output_framerate") { + display_data.target_fixed_output_framerate = std::stod(value); + continue; + } + if(key == "request_low_bw_format") { capture_status->device_specific_status.optimize_status.request_low_bw_format = std::stoi(value); continue; @@ -291,6 +320,10 @@ static bool load(const std::string path, const std::string name, ScreenInfo &top } file.close(); + + apply_version_changes_to_data(version_loaded, top_info, bottom_info, joint_info, display_data, audio_data, capture_status); + sanitize_display_data(&display_data); + return result; } @@ -390,6 +423,8 @@ static bool save(const std::string path, const std::string name, const std::stri file << "requested_3d=" << capture_status->requested_3d << std::endl; file << "interleaved_3d=" << display_data.interleaved_3d << std::endl; file << "do_ratio_cycling=" << display_data.do_ratio_cycling << std::endl; + file << "fixed_output_framerate=" << display_data.fixed_output_framerate << std::endl; + file << "target_fixed_output_framerate=" << display_data.target_fixed_output_framerate << std::endl; file << "request_low_bw_format_optimize=" << capture_status->device_specific_status.optimize_status.request_low_bw_format << std::endl; file << "request_low_bw_format_old_2ds_optimize=" << capture_status->device_specific_status.optimize_status.request_low_bw_format_old_2ds << std::endl; file << "last_connected_ds=" << display_data.last_connected_ds << std::endl; @@ -687,8 +722,9 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, const double max_time_no_frames_allowed = MAX_ALLOWED_NO_FRAME_TIME; std::chrono::time_point start_time = std::chrono::high_resolution_clock::now(); std::chrono::time_point last_valid_frame_time = start_time; + std::chrono::time_point last_output_check_time = start_time; + std::chrono::time_point last_connection_time = start_time; OutTextData out_text_data; - std::chrono::time_point last_connection_time = std::chrono::high_resolution_clock::now(); int ret_val = 0; int poll_timeout = 0; const bool endianness = is_big_endian(); @@ -762,7 +798,20 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, no_data_consecutive = NO_DATA_CONSECUTIVE_THRESHOLD; capture_data->status.video_wait.update_time_multiplier(get_time_multiplier(capture_data, no_data_consecutive >= NO_DATA_CONSECUTIVE_THRESHOLD)); - bool timed_out = !capture_data->status.video_wait.timed_lock(); + bool do_periodic_check = frontend_data.display_data.fixed_output_framerate; + double target_frametime = 1.0 / frontend_data.display_data.target_fixed_output_framerate; + bool timed_out = false; + if(do_periodic_check) { + auto curr_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = curr_time - last_output_check_time; + double to_sleep_time = target_frametime - diff.count(); + if(to_sleep_time > 0) + precise_sleep(to_sleep_time * 1000.0); + last_output_check_time = std::chrono::high_resolution_clock::now(); + timed_out = true; + } + else + timed_out = !capture_data->status.video_wait.timed_lock(); bool data_processed = false; CaptureDataSingleBuffer* data_buffer = capture_data->data_buffers.GetReaderBuffer(CAPTURE_READER_VIDEO); diff --git a/source/frontend.cpp b/source/frontend.cpp index cf939c5..38662ba 100755 --- a/source/frontend.cpp +++ b/source/frontend.cpp @@ -527,6 +527,14 @@ void reset_display_data(DisplayData* display_data) { display_data->last_connected_ds = false; display_data->interleaved_3d = false; display_data->do_ratio_cycling = false; + #if (defined(__APPLE__)) + // Should this be the default...? + //display_data->fixed_output_framerate = true; + display_data->fixed_output_framerate = false; + #else + display_data->fixed_output_framerate = false; + #endif + display_data->target_fixed_output_framerate = 60.00; } void reset_input_data(InputData* input_data) { @@ -572,7 +580,13 @@ void reset_screen_info(ScreenInfo &info) { info.top_rotation = 0; info.bot_rotation = 0; info.show_mouse = true; + #if (defined(__APPLE__)) + // Should this be the default...? + //info.v_sync_enabled = true; info.v_sync_enabled = false; + #else + info.v_sync_enabled = false; + #endif #if (defined(RASPI) || defined(ANDROID_COMPILATION) || defined(__APPLE__)) info.async = false; #else @@ -922,6 +936,13 @@ std::string save_screen_info(std::string base, const ScreenInfo &info) { return out; } +void sanitize_display_data(DisplayData* display_data) { + if(display_data->target_fixed_output_framerate < 2.0) + display_data->target_fixed_output_framerate = 2.0; + if(display_data->target_fixed_output_framerate > 999.99) + display_data->target_fixed_output_framerate = 999.99; +} + void joystick_axis_poll(std::queue &events_queue) { for(unsigned int i = 0; i < sf::Joystick::Count; i++) { if(!sf::Joystick::isConnected(i)) @@ -1144,12 +1165,28 @@ void update_connected_specific_settings(FrontendData* frontend_data, const Captu } } -void default_sleep(float wanted_ms) { +void default_sleep(double wanted_ms) { if(wanted_ms < 0) - wanted_ms = 1000.0f/USB_CHECKS_PER_SECOND; + wanted_ms = 1000.0/USB_CHECKS_PER_SECOND; sf::sleep(sf::microseconds((int)(wanted_ms * 1000))); } +void precise_sleep(double wanted_ms, double divisor, double spin_threshold_ms) { + if(wanted_ms < 0) + wanted_ms = 1000.0/USB_CHECKS_PER_SECOND; + double elapsed_ms = 0.0; + std::chrono::time_point start_time = std::chrono::high_resolution_clock::now(); + while(elapsed_ms < wanted_ms){ + double difference_ms = wanted_ms - elapsed_ms; + // Is there a better way to do this?! + if(difference_ms > spin_threshold_ms) + default_sleep(difference_ms / divisor); + std::chrono::time_point curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - start_time; + elapsed_ms = diff.count() * 1000.0; + } +} + void screen_display_thread(WindowScreen *screen) { screen->display_thread(); } diff --git a/source/utils.cpp b/source/utils.cpp index be3c6c3..29c9e33 100755 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -39,7 +39,7 @@ #define APP_VERSION_MAJOR 1 #define APP_VERSION_MINOR 3 #define APP_VERSION_REVISION 0 -#define APP_VERSION_SUBREVISION 1 +#define APP_VERSION_SUBREVISION 2 #ifdef RASPI #define APP_VERSION_LETTER R #else @@ -315,6 +315,35 @@ std::string get_version_string(bool get_letter) { return version_str; } +version_t get_version_string_number(std::string version_string) { + std::stringstream base(version_string); + std::string segment; + std::vector seglist; + version_t num_out = 0; + + try { + while(std::getline(base, segment, '.')) + seglist.push_back(segment); + + if(seglist.size() > 0) + num_out += std::stoi(seglist[0]) * 0x100 * 0x100 * 0x100; + + if(seglist.size() > 1) + num_out += std::stoi(seglist[1]) * 0x100 * 0x100; + + if(seglist.size() > 2) + num_out += std::stoi(seglist[2]) * 0x100; + + if(seglist.size() > 3) + num_out += std::stoi(seglist[3]); + } + catch(...) { + num_out = 0; + } + + return num_out; +} + std::string get_float_str_decimals(float value, int decimals) { float approx_factor = (float)(pow(0.1f, decimals) * (0.5f)); int int_part = (int)(value + approx_factor);