Implement proper frame delay and VRR for IS Nitro Emulator

This commit is contained in:
Lorenzooone 2024-08-25 09:37:30 +02:00
parent 718294ba02
commit 6f56918f3f
17 changed files with 565 additions and 135 deletions

View File

@ -44,7 +44,7 @@ endif()
include(FetchContent)
FetchContent_Declare(SFML
GIT_REPOSITORY https://github.com/SFML/SFML.git
GIT_TAG macos_fullscreen_v2.6)
GIT_TAG 2.6.x)
FetchContent_Declare(libusb1
GIT_REPOSITORY https://github.com/libusb/libusb-cmake.git
@ -301,7 +301,7 @@ execute_process(COMMAND ${CMAKE_COMMAND} --build ${TOOLS_DATA_DIR})
set(OUTPUT_NAME cc3dsfs)
add_executable(${OUTPUT_NAME} source/cc3dsfs.cpp source/utils.cpp source/audio_data.cpp source/audio.cpp source/frontend.cpp source/TextRectangle.cpp source/WindowScreen.cpp source/WindowScreen_Menu.cpp source/3dscapture_ftd3.cpp source/dscapture_ftd2.cpp source/usb_ds_3ds_capture.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/usb_is_nitro.cpp source/usb_is_nitro_capture.cpp source/usb_generic.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp)
add_executable(${OUTPUT_NAME} source/cc3dsfs.cpp source/utils.cpp source/audio_data.cpp source/audio.cpp source/frontend.cpp source/TextRectangle.cpp source/WindowScreen.cpp source/WindowScreen_Menu.cpp source/3dscapture_ftd3.cpp source/dscapture_ftd2.cpp source/usb_ds_3ds_capture.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/usb_is_nitro.cpp source/usb_is_nitro_capture.cpp source/usb_generic.cpp source/Menus/ISNitroMenu.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp)
add_dependencies(${OUTPUT_NAME} FTD3XX_BUILD_PROJECT FTD2XX_BUILD_PROJECT)
target_link_libraries(${OUTPUT_NAME} PRIVATE sfml-graphics sfml-audio sfml-window sfml-system usb-1.0 ${ftd3xx_BINARY_DIR}/${FTD3XX_SUBFOLDER}/${FTD3XX_LIB} ${ftd2xx_BINARY_DIR}/${FTD2XX_SUBFOLDER}/${FTD2XX_LIB} ${EXTRA_LIBRARIES})
target_link_directories(${OUTPUT_NAME} PRIVATE ${ftd3xx_BINARY_DIR}/${FTD3XX_SUBFOLDER} ${ftd2xx_BINARY_DIR}/${FTD2XX_SUBFOLDER})

View File

@ -3,7 +3,7 @@
cc3dsfs is a multi-platform capture and display program for [3dscapture's](https://3dscapture.com/) N3DSXL, 3DS and DS (old) capture boards written in C++.
The main goal is to offer the ability to use the Capture Card with a TV, via fullscreen mode.
Tentative IS Nitro Emulator support (for newer revisions) is also present. Though results may vary (and the amount of video delay may be significantly higher).
IS Nitro Emulator support (for newer revisions) is also present. Though results may vary (and the amount of video delay may be significantly higher based on the cable used).
## Features

View File

@ -0,0 +1,39 @@
#ifndef __ISNMENU_HPP
#define __ISNMENU_HPP
#include "OptionSelectionMenu.hpp"
#include <chrono>
#include "TextRectangle.hpp"
#include "sfml_gfx_structs.hpp"
#include "display_structs.hpp"
#include "capture_structs.hpp"
enum ISNitroMenuOutAction{
ISN_MENU_NO_ACTION,
ISN_MENU_BACK,
ISN_MENU_DELAY,
ISN_MENU_TYPE_DEC,
ISN_MENU_TYPE_INC,
};
class ISNitroMenu : public OptionSelectionMenu {
public:
ISNitroMenu(bool font_load_success, sf::Font &text_font);
~ISNitroMenu();
void prepare(float scaling_factor, int view_size_x, int view_size_y, CaptureStatus* capture_status);
void insert_data();
ISNitroMenuOutAction selected_index = ISNitroMenuOutAction::ISN_MENU_NO_ACTION;
void reset_output_option();
protected:
bool is_option_selectable(int index, int action);
bool is_option_inc_dec(int index);
void set_output_option(int index, int action);
int get_num_options();
std::string get_string_option(int index, int action);
void class_setup();
private:
int *options_indexes;
int num_enabled_options;
};
#endif

View File

@ -7,6 +7,7 @@
#include "TextRectangle.hpp"
#include "sfml_gfx_structs.hpp"
#include "display_structs.hpp"
#include "capture_structs.hpp"
enum MainMenuOutAction{
MAIN_MENU_NO_ACTION,
@ -24,6 +25,7 @@ enum MainMenuOutAction{
MAIN_MENU_EXTRA_SETTINGS,
MAIN_MENU_SHUTDOWN,
MAIN_MENU_SHORTCUT_SETTINGS,
MAIN_MENU_ISN_SETTINGS,
};
class MainMenu : public OptionSelectionMenu {
@ -31,7 +33,7 @@ public:
MainMenu(bool font_load_success, sf::Font &text_font);
~MainMenu();
void prepare(float scaling_factor, int view_size_x, int view_size_y, bool connected);
void insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcuts);
void insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcuts, CaptureConnectionType cc_type, bool connected);
MainMenuOutAction selected_index = MainMenuOutAction::MAIN_MENU_NO_ACTION;
void reset_output_option();
protected:

View File

@ -19,6 +19,7 @@
#define EXTRA_DATA_BUFFER_FTD3XX_SIZE (1 << 10)
enum CaptureConnectionType { CAPTURE_CONN_FTD3, CAPTURE_CONN_USB, CAPTURE_CONN_FTD2, CAPTURE_CONN_IS_NITRO };
enum CaptureScreensType { CAPTURE_SCREENS_BOTH, CAPTURE_SCREENS_TOP, CAPTURE_SCREENS_BOTTOM, CAPTURE_SCREENS_ENUM_END };
#pragma pack(push, 1)
@ -125,18 +126,21 @@ struct CaptureStatus {
CaptureDevice device;
std::string error_text;
bool new_error_text;
bool enabled_3d = false;
volatile int curr_in = 0;
volatile int cooldown_curr_in = FIX_PARTIAL_FIRST_FRAME_NUM;
volatile bool connected = false;
volatile bool running = true;
volatile bool close_success = true;
volatile int curr_delay = 0;
bool enabled_3d = false;
CaptureScreensType capture_type;
ConsumerMutex video_wait;
ConsumerMutex audio_wait;
};
struct CaptureData {
void* handle;
CaptureScreensType capture_type[NUM_CONCURRENT_DATA_BUFFERS];
uint64_t read[NUM_CONCURRENT_DATA_BUFFERS];
CaptureReceived capture_buf[NUM_CONCURRENT_DATA_BUFFERS];
double time_in_buf[NUM_CONCURRENT_DATA_BUFFERS];

View File

@ -5,6 +5,6 @@
#include "capture_structs.hpp"
#include "display_structs.hpp"
void convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureData* capture_data);
void convertAudioToOutput(CaptureReceived *p_in, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data);
void convertVideoToOutput(int index, VideoOutputData *p_out, CaptureData* capture_data);
void convertAudioToOutput(int index, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data);
#endif

View File

@ -7,7 +7,7 @@
enum ScreenType { TOP, BOTTOM, JOINT };
enum BottomRelativePosition { UNDER_TOP, LEFT_TOP, ABOVE_TOP, RIGHT_TOP, BOT_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 };
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 };
struct ScreenInfo {
bool is_blurred;

View File

@ -30,6 +30,7 @@
#include "ShortcutMenu.hpp"
#include "ActionSelectionMenu.hpp"
#include "ScalingRatioMenu.hpp"
#include "ISNitroMenu.hpp"
#include "display_structs.hpp"
#include "WindowCommands.hpp"
@ -68,6 +69,7 @@ public:
int check_connection_menu_result();
void end_connection_menu();
void update_ds_3ds_connection(bool changed_type);
void update_capture_specific_settings();
void update_save_menu();
void print_notification(std::string text, TextKind kind = TEXT_KIND_NORMAL);
@ -146,6 +148,7 @@ private:
LicenseMenu *license_menu;
ShortcutMenu *shortcut_menu;
ActionSelectionMenu *action_selection_menu;
ISNitroMenu *is_nitro_menu;
ScalingRatioMenu *scaling_ratio_menu;
std::vector<const CropData*> possible_crops;
std::vector<const CropData*> possible_crops_ds;
@ -219,6 +222,7 @@ private:
void fast_poll_change();
void padding_change();
void game_crop_enable_change();
void is_nitro_capture_type_change(bool positive);
void crop_value_change(int new_crop_value, bool do_print_notification = true, bool do_cycle = true);
void par_value_change(int new_par_value, bool is_top);
void offset_change(float &value, float change);
@ -273,7 +277,7 @@ private:
void setWinSize(bool is_main_thread);
bool can_setup_menu();
void setup_no_menu();
void setup_main_menu(bool reset_data = true);
void setup_main_menu(bool reset_data = true, bool skip_setup_check = false);
void setup_video_menu(bool reset_data = true);
void setup_crop_menu(bool reset_data = true);
void setup_par_menu(bool is_top, bool reset_data = true);
@ -290,6 +294,7 @@ private:
void setup_licenses_menu(bool reset_data = true);
void setup_relative_pos_menu(bool reset_data = true);
void setup_scaling_ratio_menu(bool reset_data = true);
void setup_is_nitro_menu(bool reset_data = true);
void update_connection();
};
@ -315,7 +320,8 @@ void get_par_size(int &width, int &height, float multiplier_factor, const PARDat
float get_par_mult_factor(float width, float height, float max_width, float max_height, const PARData *correction_factor, bool is_rotated);
void update_output(FrontendData* frontend_data, double frame_time = 0.0, VideoOutputData *out_buf = NULL);
void update_connected_3ds_ds(FrontendData* frontend_data, const CaptureDevice &old_cc_device, const CaptureDevice &new_cc_device);
void update_connected_specific_settings(FrontendData* frontend_data, const CaptureDevice &cc_device);
void screen_display_thread(WindowScreen *screen);
std::string get_name_non_int_mode(NonIntegerScalingModes input);
void default_sleep(int wanted_ms = -1);
void default_sleep(float wanted_ms = -1);
#endif

View File

@ -13,7 +13,7 @@ void list_devices_is_nitro(std::vector<CaptureDevice> &devices_list);
bool is_nitro_connect_usb(bool print_failed, CaptureData* capture_data, CaptureDevice* device);
void is_nitro_capture_main_loop(CaptureData* capture_data);
void usb_is_nitro_capture_cleanup(CaptureData* capture_data);
void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureDevice* capture_device);
void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureScreensType capture_type);
uint64_t usb_is_nitro_emulator_get_video_in_size(CaptureData* capture_data);
void usb_is_nitro_init();
void usb_is_nitro_close();

View File

@ -0,0 +1,136 @@
#include "ISNitroMenu.hpp"
#define NUM_TOTAL_MENU_OPTIONS (sizeof(pollable_options)/sizeof(pollable_options[0]))
struct ISNitroMenuOptionInfo {
const std::string base_name;
const std::string false_name;
const bool is_selectable;
const bool is_inc;
const std::string dec_str;
const std::string inc_str;
const ISNitroMenuOutAction inc_out_action;
const ISNitroMenuOutAction out_action;
};
static const ISNitroMenuOptionInfo is_nitro_delay_option = {
.base_name = "Delay", .false_name = "", .is_selectable = false,
.is_inc = false, .dec_str = "", .inc_str = "", .inc_out_action = ISN_MENU_NO_ACTION,
.out_action = ISN_MENU_DELAY};
static const ISNitroMenuOptionInfo is_nitro_type_option = {
.base_name = "Capture", .false_name = "", .is_selectable = true,
.is_inc = true, .dec_str = "<", .inc_str = ">", .inc_out_action = ISN_MENU_TYPE_INC,
.out_action = ISN_MENU_TYPE_DEC};
static const ISNitroMenuOptionInfo* pollable_options[] = {
&is_nitro_delay_option,
&is_nitro_type_option,
};
ISNitroMenu::ISNitroMenu(bool font_load_success, sf::Font &text_font) : OptionSelectionMenu(){
this->options_indexes = new int[NUM_TOTAL_MENU_OPTIONS];
this->initialize(font_load_success, text_font);
this->num_enabled_options = 0;
}
ISNitroMenu::~ISNitroMenu() {
delete []this->options_indexes;
}
void ISNitroMenu::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.3;
this->max_width_slack = 1.1;
this->menu_color = sf::Color(30, 30, 60, 192);
this->title = "IS Nitro Settings";
this->show_back_x = true;
this->show_x = false;
this->show_title = true;
}
void ISNitroMenu::insert_data() {
this->num_enabled_options = 0;
for(int i = 0; i < NUM_TOTAL_MENU_OPTIONS; i++) {
this->options_indexes[this->num_enabled_options] = i;
this->num_enabled_options++;
}
this->prepare_options();
}
void ISNitroMenu::reset_output_option() {
this->selected_index = ISNitroMenuOutAction::ISN_MENU_NO_ACTION;
}
void ISNitroMenu::set_output_option(int index, int action) {
if(index == BACK_X_OUTPUT_OPTION)
this->selected_index = ISN_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;
}
int ISNitroMenu::get_num_options() {
return this->num_enabled_options;
}
std::string ISNitroMenu::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;
if(action == FALSE_ACTION)
return pollable_options[this->options_indexes[index]]->false_name;
return pollable_options[this->options_indexes[index]]->base_name;
}
bool ISNitroMenu::is_option_selectable(int index, int action) {
return pollable_options[this->options_indexes[index]]->is_selectable;
}
bool ISNitroMenu::is_option_inc_dec(int index) {
return pollable_options[this->options_indexes[index]]->is_inc;
}
static std::string get_capture_type_name(CaptureScreensType capture_type) {
switch(capture_type) {
case CAPTURE_SCREENS_TOP:
return "Top Screen";
case CAPTURE_SCREENS_BOTTOM:
return "Bottom Screen";
default:
return "Both Screens";
}
}
void ISNitroMenu::prepare(float menu_scaling_factor, int view_size_x, int view_size_y, CaptureStatus* capture_status) {
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 ISN_MENU_DELAY:
this->labels[index]->setText(this->setTextOptionInt(real_index, capture_status->curr_delay));
break;
case ISN_MENU_TYPE_DEC:
this->labels[index]->setText(this->setTextOptionString(real_index, get_capture_type_name(capture_status->capture_type)));
break;
default:
break;
}
}
this->base_prepare(menu_scaling_factor, view_size_x, view_size_y);
}

View File

@ -12,6 +12,7 @@ struct MainMenuOptionInfo {
const bool active_bottom_screen;
const bool enabled_normal_mode;
const bool enabled_mono_mode;
const bool is_cc_specific;
const MainMenuOutAction out_action;
};
@ -19,107 +20,114 @@ static const MainMenuOptionInfo connect_option = {
.base_name = "Disconnect", .false_name = "Connect",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_OPEN};
static const MainMenuOptionInfo windowed_option = {
.base_name = "Windowed Mode", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = false,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = false,
.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false,
.out_action = MAIN_MENU_FULLSCREEN};
static const MainMenuOptionInfo fullscreen_option = {
.base_name = "Fullscreen Mode", .false_name = "",
.active_fullscreen = false, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = false,
.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false,
.out_action = MAIN_MENU_FULLSCREEN};
static const MainMenuOptionInfo join_screens_option = {
.base_name = "Join Screens", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = false, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = false,
.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false,
.out_action = MAIN_MENU_SPLIT};
static const MainMenuOptionInfo split_screens_option = {
.base_name = "Split Screens", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = false, .active_bottom_screen = false,
.enabled_normal_mode = true, .enabled_mono_mode = false,
.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false,
.out_action = MAIN_MENU_SPLIT};
static const MainMenuOptionInfo video_settings_option = {
.base_name = "Video Settings", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_VIDEO_SETTINGS};
static const MainMenuOptionInfo quit_option = {
.base_name = "Quit Application", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = false,
.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false,
.out_action = MAIN_MENU_QUIT_APPLICATION};
static const MainMenuOptionInfo audio_settings_option = {
.base_name = "Audio Settings", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_AUDIO_SETTINGS};
static const MainMenuOptionInfo save_profiles_option = {
.base_name = "Save Profile", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_SAVE_PROFILES};
static const MainMenuOptionInfo load_profiles_option = {
.base_name = "Load Profile", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_LOAD_PROFILES};
static const MainMenuOptionInfo status_option = {
.base_name = "Status", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_STATUS};
static const MainMenuOptionInfo licenses_option = {
.base_name = "Licenses", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_LICENSES};
static const MainMenuOptionInfo extra_settings_option = {
.base_name = "Extra Settings", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = false, .enabled_mono_mode = true,
.enabled_normal_mode = false, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_EXTRA_SETTINGS};
static const MainMenuOptionInfo shortcut_option = {
.base_name = "Shortcuts", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_SHORTCUT_SETTINGS};
static const MainMenuOptionInfo shutdown_option = {
.base_name = "Shutdown", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = false, .enabled_mono_mode = true,
.enabled_normal_mode = false, .enabled_mono_mode = true, .is_cc_specific = false,
.out_action = MAIN_MENU_SHUTDOWN};
static const MainMenuOptionInfo isn_settings_option = {
.base_name = "Is Nitro Settings", .false_name = "",
.active_fullscreen = true, .active_windowed_screen = true,
.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true,
.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = true,
.out_action = MAIN_MENU_ISN_SETTINGS};
static const MainMenuOptionInfo* pollable_options[] = {
&connect_option,
&windowed_option,
@ -132,6 +140,7 @@ static const MainMenuOptionInfo* pollable_options[] = {
&load_profiles_option,
&shortcut_option,
&status_option,
&isn_settings_option,
&licenses_option,
&extra_settings_option,
&quit_option,
@ -164,7 +173,13 @@ void MainMenu::class_setup() {
this->show_title = true;
}
void MainMenu::insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcut) {
static bool check_cc_specific_option(const MainMenuOptionInfo* option, CaptureConnectionType cc_type) {
if((option->out_action == MAIN_MENU_ISN_SETTINGS) && (cc_type == CAPTURE_CONN_IS_NITRO))
return true;
return false;
}
void MainMenu::insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcut, CaptureConnectionType cc_type, bool connected) {
this->num_enabled_options = 0;
for(int i = 0; i < NUM_TOTAL_MENU_OPTIONS; i++) {
bool valid = true;
@ -182,6 +197,8 @@ void MainMenu::insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_
valid = valid && pollable_options[i]->enabled_mono_mode;
else
valid = valid && pollable_options[i]->enabled_normal_mode;
if(pollable_options[i]->is_cc_specific)
valid = valid && connected && check_cc_specific_option(pollable_options[i], cc_type);
if((pollable_options[i]->out_action == MAIN_MENU_SHORTCUT_SETTINGS) && (!enable_shortcut))
valid = false;
if(valid) {

View File

@ -109,6 +109,7 @@ void WindowScreen::init_menus() {
this->shortcut_menu = new ShortcutMenu(this->font_load_success, this->text_font);
this->action_selection_menu = new ActionSelectionMenu(this->font_load_success, this->text_font);
this->scaling_ratio_menu = new ScalingRatioMenu(this->font_load_success, this->text_font);
this->is_nitro_menu = new ISNitroMenu(this->font_load_success, this->text_font);
}
void WindowScreen::destroy_menus() {
@ -130,6 +131,7 @@ void WindowScreen::destroy_menus() {
delete this->shortcut_menu;
delete this->action_selection_menu;
delete this->scaling_ratio_menu;
delete this->is_nitro_menu;
}
void WindowScreen::set_close(int ret_val) {
@ -174,6 +176,15 @@ void WindowScreen::fast_poll_change() {
this->print_notification_on_off("Slow Poll", this->display_data->fast_poll);
}
void WindowScreen::is_nitro_capture_type_change(bool positive) {
int new_value = (int)(this->capture_status->capture_type);
if(positive)
new_value += 1;
else
new_value += CAPTURE_SCREENS_ENUM_END - 1;
this->capture_status->capture_type = static_cast<CaptureScreensType>(new_value % CAPTURE_SCREENS_ENUM_END);
}
void WindowScreen::padding_change() {
if(this->m_info.is_fullscreen)
return;
@ -615,14 +626,14 @@ void WindowScreen::setup_no_menu() {
this->last_menu_change_time = std::chrono::high_resolution_clock::now();
}
void WindowScreen::setup_main_menu(bool reset_data) {
if(!this->can_setup_menu())
void WindowScreen::setup_main_menu(bool reset_data, bool skip_setup_check) {
if((!skip_setup_check) && (!this->can_setup_menu()))
return;
if(this->curr_menu != MAIN_MENU_TYPE) {
this->curr_menu = MAIN_MENU_TYPE;
if(reset_data)
this->main_menu->reset_data();
this->main_menu->insert_data(this->m_stype, this->m_info.is_fullscreen, this->display_data->mono_app_mode, is_shortcut_valid());
this->main_menu->insert_data(this->m_stype, this->m_info.is_fullscreen, this->display_data->mono_app_mode, is_shortcut_valid(), this->capture_status->device.cc_type, this->capture_status->connected);
this->last_menu_change_time = std::chrono::high_resolution_clock::now();
}
}
@ -900,6 +911,18 @@ void WindowScreen::setup_scaling_ratio_menu(bool reset_data) {
}
}
void WindowScreen::setup_is_nitro_menu(bool reset_data) {
if(!this->can_setup_menu())
return;
if(this->curr_menu != ISN_MENU_TYPE) {
this->curr_menu = ISN_MENU_TYPE;
if(reset_data)
this->is_nitro_menu->reset_data();
this->is_nitro_menu->insert_data();
this->last_menu_change_time = std::chrono::high_resolution_clock::now();
}
}
void WindowScreen::update_save_menu() {
if(this->curr_menu == SAVE_MENU_TYPE) {
this->curr_menu = DEFAULT_MENU_TYPE;
@ -1234,6 +1257,10 @@ void WindowScreen::poll(bool do_everything) {
this->setup_shortcuts_menu();
done = true;
break;
case MAIN_MENU_ISN_SETTINGS:
this->setup_is_nitro_menu();
done = true;
break;
case MAIN_MENU_SHUTDOWN:
this->set_close(1);
this->setup_no_menu();
@ -1771,6 +1798,30 @@ void WindowScreen::poll(bool do_everything) {
continue;
}
break;
case ISN_MENU_TYPE:
if(this->is_nitro_menu->poll(event_data)) {
switch(this->is_nitro_menu->selected_index) {
case ISN_MENU_BACK:
this->setup_main_menu(false);
done = true;
break;
case ISN_MENU_NO_ACTION:
break;
case ISN_MENU_DELAY:
break;
case ISN_MENU_TYPE_DEC:
this->is_nitro_capture_type_change(false);
break;
case ISN_MENU_TYPE_INC:
this->is_nitro_capture_type_change(true);
break;
default:
break;
}
this->is_nitro_menu->reset_output_option();
continue;
}
break;
default:
break;
}
@ -1800,6 +1851,15 @@ void WindowScreen::update_ds_3ds_connection(bool changed_type) {
this->future_operations.call_crop = true;
}
void WindowScreen::update_capture_specific_settings() {
if(this->curr_menu == MAIN_MENU_TYPE) {
this->setup_no_menu();
this->setup_main_menu(true, true);
}
if(this->curr_menu == ISN_MENU_TYPE)
this->setup_no_menu();
}
int WindowScreen::load_data() {
int ret_val = this->m_prepare_load;
this->m_prepare_load = 0;
@ -1985,6 +2045,9 @@ void WindowScreen::prepare_menu_draws(int view_size_x, int view_size_y) {
case SCALING_RATIO_MENU_TYPE:
this->scaling_ratio_menu->prepare(this->loaded_info.menu_scaling_factor, view_size_x, view_size_y, &this->loaded_info);
break;
case ISN_MENU_TYPE:
this->is_nitro_menu->prepare(this->loaded_info.menu_scaling_factor, view_size_x, view_size_y, this->capture_status);
break;
default:
break;
}
@ -2052,6 +2115,9 @@ void WindowScreen::execute_menu_draws() {
case SCALING_RATIO_MENU_TYPE:
this->scaling_ratio_menu->draw(this->loaded_info.menu_scaling_factor, this->m_win);
break;
case ISN_MENU_TYPE:
this->is_nitro_menu->draw(this->loaded_info.menu_scaling_factor, this->m_win);
break;
default:
break;
}

View File

@ -57,7 +57,7 @@ static void SuccessConnectionOutTextGenerator(OutTextData &out_text_data, Captur
}
}
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, ExtraButtonShortcuts* extra_button_shortcuts) {
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, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status) {
std::ifstream file(path + name);
std::string line;
@ -110,6 +110,11 @@ static bool load(const std::string path, const std::string name, ScreenInfo &top
continue;
}
if(key == "is_screen_capture_type") {
capture_status->capture_type = static_cast<CaptureScreensType>(std::stoi(value) % CaptureScreensType::CAPTURE_SCREENS_ENUM_END);
continue;
}
if(audio_data->load_audio_data(key, value))
continue;
}
@ -125,7 +130,9 @@ static bool load(const std::string path, const std::string name, ScreenInfo &top
return result;
}
static void defaults_reload(FrontendData *frontend_data, AudioData* audio_data, ExtraButtonShortcuts* extra_button_shortcuts) {
static void defaults_reload(FrontendData *frontend_data, AudioData* audio_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status) {
capture_status->enabled_3d = false;
capture_status->capture_type = CAPTURE_SCREENS_BOTH;
reset_screen_info(frontend_data->top_screen->m_info);
reset_screen_info(frontend_data->bot_screen->m_info);
reset_screen_info(frontend_data->joint_screen->m_info);
@ -138,11 +145,11 @@ static void defaults_reload(FrontendData *frontend_data, AudioData* audio_data,
frontend_data->reload = true;
}
static void load_layout_file(int load_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, bool skip_io, bool do_print) {
static void load_layout_file(int load_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status, bool skip_io, bool do_print) {
if(skip_io)
return;
defaults_reload(frontend_data, audio_data, extra_button_shortcuts);
defaults_reload(frontend_data, audio_data, extra_button_shortcuts, capture_status);
if(load_index == SIMPLE_RESET_DATA_INDEX) {
UpdateOutText(out_text_data, "Reset detected. Defaults re-loaded", "Reset detected\nDefaults re-loaded", TEXT_KIND_WARNING);
@ -152,16 +159,16 @@ static void load_layout_file(int load_index, FrontendData *frontend_data, AudioD
bool name_load_success = false;
std::string layout_name = LayoutNameGenerator(load_index);
std::string layout_path = LayoutPathGenerator(load_index);
bool op_success = load(layout_path, layout_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts);
bool op_success = load(layout_path, layout_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts, capture_status);
if(do_print && op_success) {
std::string load_name = load_layout_name(load_index, name_load_success);
UpdateOutText(out_text_data, "Layout loaded from: " + layout_path + layout_name, "Layout " + load_name + " loaded", TEXT_KIND_SUCCESS);
}
else if(!op_success)
defaults_reload(frontend_data, audio_data, extra_button_shortcuts);
defaults_reload(frontend_data, audio_data, extra_button_shortcuts, capture_status);
}
static bool save(const std::string path, const std::string name, const std::string save_name, const ScreenInfo &top_info, const ScreenInfo &bottom_info, const ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts) {
static bool save(const std::string path, const std::string name, const std::string save_name, const ScreenInfo &top_info, const ScreenInfo &bottom_info, const ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status) {
#if (!defined(_MSC_VER)) || (_MSC_VER > 1916)
std::filesystem::create_directories(path);
#else
@ -183,13 +190,14 @@ static bool save(const std::string path, const std::string name, const std::stri
file << "last_connected_ds=" << display_data.last_connected_ds << std::endl;
file << "extra_button_enter_short=" << extra_button_shortcuts->enter_shortcut->cmd << std::endl;
file << "extra_button_page_up_short=" << extra_button_shortcuts->page_up_shortcut->cmd << std::endl;
file << "is_screen_capture_type=" << capture_status->capture_type << std::endl;
file << audio_data->save_audio_data();
file.close();
return true;
}
static void save_layout_file(int save_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, bool skip_io, bool do_print) {
static void save_layout_file(int save_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status, bool skip_io, bool do_print) {
if(skip_io)
return;
@ -200,7 +208,7 @@ static void save_layout_file(int save_index, FrontendData *frontend_data, AudioD
std::string save_name = load_layout_name(save_index, name_load_success);
std::string layout_name = LayoutNameGenerator(save_index);
std::string layout_path = LayoutPathGenerator(save_index);
bool op_success = save(layout_path, layout_name, save_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts);
bool op_success = save(layout_path, layout_name, save_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts, capture_status);
if(do_print && op_success) {
UpdateOutText(out_text_data, "Layout saved to: " + layout_path + layout_name, "Layout " + save_name + " saved", TEXT_KIND_SUCCESS);
}
@ -223,7 +231,7 @@ static void soundCall(AudioData *audio_data, CaptureData* capture_data) {
if((capture_data->read[curr_out] > get_video_in_size(capture_data)) && (loaded_samples < MAX_MAX_AUDIO_LATENCY)) {
int n_samples = get_audio_n_samples(capture_data, capture_data->read[curr_out]);
double out_time = capture_data->time_in_buf[curr_out];
convertAudioToOutput(&capture_data->capture_buf[curr_out], out_buf[audio_buf_counter], n_samples, endianness, capture_data);
convertAudioToOutput(curr_out, out_buf[audio_buf_counter], n_samples, endianness, capture_data);
audio.samples.emplace(out_buf[audio_buf_counter], n_samples, out_time);
if(++audio_buf_counter == NUM_CONCURRENT_AUDIO_BUFFERS) {
audio_buf_counter = 0;
@ -307,7 +315,7 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data,
frontend_data.bot_screen = bot_screen;
frontend_data.joint_screen = joint_screen;
load_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, false);
load_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, false);
// Due to the risk for seizures, at the start of the program, set BFI to false!
top_screen->m_info.bfi = false;
bot_screen->m_info.bfi = false;
@ -322,6 +330,7 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data,
std::thread joint_thread(screen_display_thread, joint_screen);
capture_data->status.connected = connect(true, capture_data, &frontend_data);
bool last_connected = capture_data->status.connected;
SuccessConnectionOutTextGenerator(out_text_data, capture_data);
while(capture_data->status.running) {
@ -337,13 +346,16 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data,
VideoOutputData *chosen_buf = out_buf;
bool blank_out = false;
if(capture_data->status.connected) {
if(!last_connected)
update_connected_specific_settings(&frontend_data, capture_data->status.device);
last_connected = true;
bool timed_out = !capture_data->status.video_wait.timed_lock();
curr_out = (capture_data->status.curr_in - 1 + NUM_CONCURRENT_DATA_BUFFERS) % NUM_CONCURRENT_DATA_BUFFERS;
if((!capture_data->status.cooldown_curr_in) && (curr_out != prev_out)) {
last_frame_time = capture_data->time_in_buf[curr_out];
if(capture_data->read[curr_out] >= get_video_in_size(capture_data)) {
convertVideoToOutput(&capture_data->capture_buf[curr_out], out_buf, capture_data);
convertVideoToOutput(curr_out, out_buf, capture_data);
num_allowed_blanks = MAX_ALLOWED_BLANKS;
}
else {
@ -355,11 +367,14 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data,
}
}
}
else if(capture_data->status.cooldown_curr_in)
else if((capture_data->status.cooldown_curr_in) || timed_out)
blank_out = true;
prev_out = curr_out;
}
else {
if(last_connected)
update_connected_specific_settings(&frontend_data, capture_data->status.device);
last_connected = false;
default_sleep();
blank_out = true;
}
@ -392,12 +407,12 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data,
if((load_index = top_screen->load_data()) || (load_index = bot_screen->load_data()) || (load_index = joint_screen->load_data())) {
// This value should only be loaded when starting the program...
bool previous_last_connected_ds = frontend_data.display_data.last_connected_ds;
load_layout_file(load_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, true);
load_layout_file(load_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, true);
frontend_data.display_data.last_connected_ds = previous_last_connected_ds;
}
if((save_index = top_screen->save_data()) || (save_index = bot_screen->save_data()) || (save_index = joint_screen->save_data())) {
save_layout_file(save_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, true);
save_layout_file(save_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, true);
top_screen->update_save_menu();
bot_screen->update_save_menu();
joint_screen->update_save_menu();
@ -433,7 +448,7 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data,
bot_screen->after_thread_join();
joint_screen->after_thread_join();
save_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, false);
save_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, false);
if(!out_text_data.consumed) {
ConsoleOutText(out_text_data.full_text);

View File

@ -7,7 +7,8 @@
#include <cstring>
void convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureData* capture_data) {
void convertVideoToOutput(int index, VideoOutputData *p_out, CaptureData* capture_data) {
CaptureReceived *p_in = &capture_data->capture_buf[index];
#ifdef USE_FTD3
if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD3)
ftd3_convertVideoToOutput(p_in, p_out, capture_data->status.enabled_3d);
@ -22,13 +23,14 @@ void convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, Capture
#endif
#ifdef USE_IS_NITRO_USB
if(capture_data->status.device.cc_type == CAPTURE_CONN_IS_NITRO)
usb_is_nitro_convertVideoToOutput(p_in, p_out, &capture_data->status.device);
usb_is_nitro_convertVideoToOutput(p_in, p_out, capture_data->capture_type[index]);
#endif
}
void convertAudioToOutput(CaptureReceived *p_in, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data) {
void convertAudioToOutput(int index, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data) {
if(!capture_data->status.device.has_audio)
return;
CaptureReceived *p_in = &capture_data->capture_buf[index];
uint8_t* base_ptr = NULL;
#ifdef USE_FTD3
if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD3) {

View File

@ -786,9 +786,17 @@ void update_connected_3ds_ds(FrontendData* frontend_data, const CaptureDevice &o
}
}
void default_sleep(int wanted_ms) {
void update_connected_specific_settings(FrontendData* frontend_data, const CaptureDevice &cc_device) {
if(cc_device.cc_type == CAPTURE_CONN_IS_NITRO) {
frontend_data->top_screen->update_capture_specific_settings();
frontend_data->bot_screen->update_capture_specific_settings();
frontend_data->joint_screen->update_capture_specific_settings();
}
}
void default_sleep(float wanted_ms) {
if(wanted_ms < 0)
sf::sleep(sf::milliseconds(1000/USB_CHECKS_PER_SECOND));
sf::sleep(sf::milliseconds(1000.0/USB_CHECKS_PER_SECOND));
else
sf::sleep(sf::milliseconds(wanted_ms));
}

View File

@ -247,7 +247,7 @@ int DisableLca2(libusb_device_handle *handle) {
ret = WriteNecMemU16(handle, 0xF84000A, 1);
if(ret < 0)
return ret;
default_sleep(2000);
//default_sleep(2000);
return WriteNecMemU16(handle, 0xF84000A, 0);
}
@ -290,6 +290,7 @@ int UpdateFrameForwardEnable(libusb_device_handle *handle, uint8_t value) {
}
int ReadFrame(libusb_device_handle *handle, uint8_t* buf, int length) {
// Maybe making this async would be better for lower end hardware...
int num_bytes = 0;
int ret = bulk_in(handle, &usb_is_nitro_desc, buf, length, &num_bytes);
if(num_bytes != length)

View File

@ -30,7 +30,7 @@
#define SERIAL_NUMBER_SIZE (IS_NITRO_REAL_SERIAL_NUMBER_SIZE + 1)
#define NUM_CONSECUTIVE_FRAMES 32
#define FRAME_BUFFER_SIZE 32
enum usb_capture_status {
USB_CAPTURE_SUCCESS = 0,
@ -40,9 +40,9 @@ enum usb_capture_status {
USB_CAPTURE_ERROR
};
static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames);
static int StartCapture(libusb_device_handle *handle, int *out_frame_count);
static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames);
static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames, CaptureScreensType capture_type);
static int StartCapture(libusb_device_handle *handle, uint16_t *out_frame_count, float* single_frame_time, CaptureScreensType capture_type);
static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames, CaptureScreensType capture_type);
static std::string get_serial(const is_nitro_usb_device* usb_device_desc, libusb_device_handle *handle, int &curr_serial_extra_id) {
uint8_t data[SERIAL_NUMBER_SIZE];
@ -50,13 +50,18 @@ static std::string get_serial(const is_nitro_usb_device* usb_device_desc, libusb
bool conn_success = true;
if(libusb_set_configuration(handle, usb_device_desc->default_config) != LIBUSB_SUCCESS)
conn_success = false;
//if(libusb_reset_device(handle) != LIBUSB_SUCCESS)
// conn_success = false;
if(conn_success && libusb_claim_interface(handle, usb_device_desc->default_interface) != LIBUSB_SUCCESS)
conn_success = false;
if(conn_success && EndCapture(handle, false, 0) != LIBUSB_SUCCESS)
if(conn_success && EndCapture(handle, false, 0, CAPTURE_SCREENS_BOTH) != LIBUSB_SUCCESS)
conn_success = false;
if(conn_success && (GetDeviceSerial(handle, data) == LIBUSB_SUCCESS)) {
if(conn_success && (GetDeviceSerial(handle, data) != LIBUSB_SUCCESS)) {
int ret = 0;
while(ret >= 0)
ret = drain_frames(handle, FRAME_BUFFER_SIZE * 2, 0, CAPTURE_SCREENS_TOP);
if((GetDeviceSerial(handle, data) != LIBUSB_SUCCESS))
conn_success = false;
}
if(conn_success) {
data[IS_NITRO_REAL_SERIAL_NUMBER_SIZE] = '\0';
serial_str = std::string((const char*)data);
}
@ -165,71 +170,120 @@ bool is_nitro_connect_usb(bool print_failed, CaptureData* capture_data, CaptureD
return true;
}
static uint64_t _is_nitro_emulator_get_video_in_size() {
static uint64_t _is_nitro_emulator_get_video_in_size(CaptureScreensType capture_type) {
if((capture_type == CAPTURE_SCREENS_TOP) || (capture_type == CAPTURE_SCREENS_BOTTOM))
return sizeof(ISNitroEmulatorVideoInputData) / 2;
return sizeof(ISNitroEmulatorVideoInputData);
}
static uint64_t get_capture_size() {
return sizeof(ISNitroCaptureReceived);
}
uint64_t usb_is_nitro_emulator_get_video_in_size(CaptureData* capture_data) {
return _is_nitro_emulator_get_video_in_size();
return _is_nitro_emulator_get_video_in_size(capture_data->status.capture_type);
}
static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames) {
static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames, CaptureScreensType capture_type) {
ISNitroEmulatorVideoInputData video_in_buffer;
for (int i = start_frames; i < num_frames; i++) {
int ret = ReadFrame(handle, (uint8_t*)&video_in_buffer, _is_nitro_emulator_get_video_in_size());
int ret = ReadFrame(handle, (uint8_t*)&video_in_buffer, _is_nitro_emulator_get_video_in_size(capture_type));
if(ret < 0)
return ret;
}
return LIBUSB_SUCCESS;
}
static int StartCapture(libusb_device_handle *handle, int *out_frame_count) {
static int set_capture_mode(libusb_device_handle *handle, CaptureScreensType capture_type) {
uint8_t capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_BOTH;
if(capture_type == CAPTURE_SCREENS_TOP)
capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_TOP;
if(capture_type == CAPTURE_SCREENS_BOTTOM)
capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_BOTTOM;
return UpdateFrameForwardConfig(handle, IS_NITRO_FORWARD_CONFIG_COLOR_RGB24 | capture_mode_flag | IS_NITRO_FORWARD_CONFIG_RATE_FULL);
}
static int StartCapture(libusb_device_handle *handle, uint16_t &out_frame_count, float &single_frame_time, CaptureScreensType capture_type) {
int ret = DisableLca2(handle);
if(ret < 0)
return ret;
ret = UpdateFrameForwardConfig(handle, IS_NITRO_FORWARD_CONFIG_COLOR_RGB24 | IS_NITRO_FORWARD_CONFIG_MODE_BOTH | IS_NITRO_FORWARD_CONFIG_RATE_FULL);
ret = set_capture_mode(handle, capture_type);
if(ret < 0)
return ret;
ret = SetForwardFrameCount(handle, NUM_CONSECUTIVE_FRAMES);
ret = SetForwardFrameCount(handle, FRAME_BUFFER_SIZE);
if(ret < 0)
return ret;
ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_DISABLE);
// Reset this in case it's high. At around 0xFFFF, reading from the USB DMA seems to fail...
ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_RESTART | IS_NITRO_FORWARD_ENABLE_ENABLE);
if(ret < 0)
return ret;
ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_ENABLE);
if(ret < 0)
return ret;
// Get to the closest next frame
auto clock_start = std::chrono::high_resolution_clock::now();
uint16_t oldFrameCount;
uint16_t newFrameCount;
ret = GetFrameCounter(handle, &oldFrameCount);
if(ret < 0)
return ret;
ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_ENABLE | IS_NITRO_FORWARD_ENABLE_RESTART);
if(ret < 0)
return ret;
uint16_t newFrameCount;
while (true) {
newFrameCount = oldFrameCount;
while(newFrameCount == oldFrameCount) {
ret = GetFrameCounter(handle, &newFrameCount);
if(ret < 0)
return ret;
if (newFrameCount < 64 && newFrameCount != oldFrameCount && newFrameCount != oldFrameCount + 1) {
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
// If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up
if(diff.count() > 0.2)
break;
}
}
ret = StartUsbCaptureDma(handle);
// Get to the next modulo 32 frame.
// We also do this to measure the time that is needed for each frame...
// To do so, a minimum of 4 frames is required (FRAME_BUFFER_SIZE - 1 + 4)
clock_start = std::chrono::high_resolution_clock::now();
ret = GetFrameCounter(handle, &oldFrameCount);
if(ret < 0)
return ret;
ret = drain_frames(handle, newFrameCount, 0);
*out_frame_count = newFrameCount;
uint16_t targetFrameCount = (newFrameCount + FRAME_BUFFER_SIZE + 3) & (~(FRAME_BUFFER_SIZE - 1));
while(oldFrameCount != targetFrameCount) {
ret = GetFrameCounter(handle, &oldFrameCount);
if(ret < 0)
return ret;
// Placing a sleep of some kind here would be much better...
// Though this is only executed for a small time when first connecting...
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
// If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up
if(diff.count() > 1.0)
break;
}
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
// Sometimes the upper 8 bits aren't updated... Use only the lower 8 bits.
newFrameCount &= 0xFF;
oldFrameCount &= 0xFF;
int frame_diff = ((int)oldFrameCount) - ((int)newFrameCount);
if(frame_diff < 0)
frame_diff += 1 << 8;
out_frame_count = oldFrameCount;
// Determine how much time a single frame takes. We'll use it for sleeps
if(frame_diff == 0)
single_frame_time = 0;
else
single_frame_time = diff.count() / frame_diff;
// Start the actual DMA
if(single_frame_time > 0) {
ret = StartUsbCaptureDma(handle);
if(ret < 0)
return ret;
}
return ret;
}
static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames) {
static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames, CaptureScreensType capture_type) {
int ret = 0;
if(do_drain_frames)
ret = drain_frames(handle, NUM_CONSECUTIVE_FRAMES, start_frames);
ret = drain_frames(handle, FRAME_BUFFER_SIZE, start_frames, capture_type);
if(ret < 0)
return ret;
ret = StopUsbCaptureDma(handle);
@ -238,38 +292,98 @@ static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int st
return UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_DISABLE);
}
int reset_capture_frames(libusb_device_handle* handle, int &frame_counter, uint16_t &total_frame_counter) {
total_frame_counter += 1;
frame_counter += 1;
static void frame_wait(float single_frame_time, std::chrono::time_point<std::chrono::high_resolution_clock> clock_last_reset, int curr_frame_counter, int last_frame_counter) {
if(curr_frame_counter == 0)
return;
auto curr_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = curr_time - clock_last_reset;
float expected_time = single_frame_time * curr_frame_counter;
// If the current time is too low, sleep a bit to make sure we don't overrun the framerate counter
// Don't do it regardless of the situation, and only in small increments...
// Otherwise there is the risk of sleeping too much
while((diff.count() < expected_time) && ((expected_time - diff.count()) > (single_frame_time / 4)) && (!(last_frame_counter & (FRAME_BUFFER_SIZE - 1)))) {
default_sleep((expected_time - diff.count()) / 4);
curr_time = std::chrono::high_resolution_clock::now();
diff = curr_time - clock_last_reset;
}
}
if(frame_counter == NUM_CONSECUTIVE_FRAMES) {
int reset_capture_frames(libusb_device_handle* handle, uint16_t &curr_frame_counter, uint16_t &last_frame_counter, float &single_frame_time, std::chrono::time_point<std::chrono::high_resolution_clock> &clock_last_reset, CaptureScreensType &curr_capture_type, CaptureScreensType wanted_capture_type, int multiplier) {
curr_frame_counter += 1;
if(curr_frame_counter == FRAME_BUFFER_SIZE) {
int ret = StopUsbCaptureDma(handle);
if(ret < 0)
return ret;
/*
uint16_t internalFrameCount = -1;
int diff;
do {
if (internalFrameCount != -1)
default_sleep(8000);
ret = GetFrameCounter(handle, &internalFrameCount);
// If the user requests a mode change, accomodate them.
// Though it may lag for a bit...
if(wanted_capture_type != curr_capture_type) {
curr_capture_type = wanted_capture_type;
ret = set_capture_mode(handle, curr_capture_type);
if(ret < 0)
return ret;
}
diff = internalFrameCount - total_frame_counter;
if(diff > 32768)
diff -= 1 << 16;
} while(diff <= 0);
*/
uint16_t internalFrameCount = 0;
uint16_t full_internalFrameCount = 0;
int frame_diff = 0;
int diff_target = FRAME_BUFFER_SIZE * multiplier;
do {
// Check how many frames have passed...
ret = GetFrameCounter(handle, &internalFrameCount);
full_internalFrameCount = internalFrameCount;
// Sometimes the upper 8 bits aren't updated... Use only the lower 8 bits.
internalFrameCount &= 0xFF;
if(ret < 0)
return ret;
frame_diff = internalFrameCount - last_frame_counter;
if(frame_diff < 0)
frame_diff += 1 << 8;
// If the frames haven't advanced, the DS is either turned off or sleeping. If so, avoid locking up
if(frame_diff == 0)
break;
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_last_reset;
// If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up
if(diff.count() > (1.0 * multiplier)) {
frame_diff = 0;
break;
}
// Exit if enough frames have passed, or if there currently is some delay.
// Exiting early makes it possible to catch up to the DMA, if we're behind.
} while((frame_diff < diff_target) && (!(last_frame_counter & (FRAME_BUFFER_SIZE - 1))));
// Determine how much time a single frame takes. We'll use it for sleeps
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_last_reset;
if(frame_diff == 0)
single_frame_time = 0;
else
single_frame_time = diff.count() / (frame_diff / ((float)multiplier));
clock_last_reset = curr_time;
// Save the current frame counter's 8 LSB
last_frame_counter = internalFrameCount;
// If we're nearing 0xFFFF for the frame counter, reset it.
// It's a problematic value for DMA reading
if(frame_diff && (full_internalFrameCount >= 0xF000)) {
ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_RESTART | IS_NITRO_FORWARD_ENABLE_ENABLE);
if(ret < 0)
return ret;
clock_last_reset = std::chrono::high_resolution_clock::now();
}
ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_ENABLE);
if(ret < 0)
return ret;
frame_counter = 0;
ret = StartUsbCaptureDma(handle);
if(ret < 0)
return ret;
curr_frame_counter = 0;
// Start the actual DMA
if(single_frame_time > 0) {
ret = StartUsbCaptureDma(handle);
if(ret < 0)
return ret;
}
}
return LIBUSB_SUCCESS;
}
@ -278,42 +392,55 @@ void is_nitro_capture_main_loop(CaptureData* capture_data) {
if(!usb_is_initialized())
return;
libusb_device_handle *handle = (libusb_device_handle*)capture_data->handle;
int frame_counter = 0;
int ret = StartCapture(handle, &frame_counter);
uint16_t total_frame_counter = frame_counter;
uint16_t last_frame_counter = 0;
float single_frame_time = 0;
uint16_t curr_frame_counter = 0;
CaptureScreensType curr_capture_type = capture_data->status.capture_type;
int ret = StartCapture(handle, last_frame_counter, single_frame_time, curr_capture_type);
if(ret < 0) {
capture_error_print(true, capture_data, "Capture Start: Failed");
return;
}
int inner_curr_in = 0;
auto clock_start = std::chrono::high_resolution_clock::now();
auto clock_last_reset = std::chrono::high_resolution_clock::now();
while(capture_data->status.connected && capture_data->status.running) {
ret = ReadFrame(handle, (uint8_t*)&capture_data->capture_buf[inner_curr_in], _is_nitro_emulator_get_video_in_size());
if(ret < 0) {
capture_error_print(true, capture_data, "Disconnected: Read error");
break;
}
frame_wait(single_frame_time, clock_last_reset, curr_frame_counter, last_frame_counter);
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
ret = reset_capture_frames(handle, frame_counter, total_frame_counter);
if(single_frame_time > 0) {
ret = ReadFrame(handle, (uint8_t*)&capture_data->capture_buf[inner_curr_in], _is_nitro_emulator_get_video_in_size(curr_capture_type));
if(ret < 0) {
capture_error_print(true, capture_data, "Disconnected: Read error");
break;
}
// Output to the other threads...
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
clock_start = curr_time;
capture_data->time_in_buf[inner_curr_in] = diff.count();
capture_data->read[inner_curr_in] = _is_nitro_emulator_get_video_in_size(curr_capture_type);
capture_data->capture_type[inner_curr_in] = curr_capture_type;
inner_curr_in = (inner_curr_in + 1) % NUM_CONCURRENT_DATA_BUFFERS;
if(capture_data->status.cooldown_curr_in)
capture_data->status.cooldown_curr_in = capture_data->status.cooldown_curr_in - 1;
capture_data->status.curr_in = inner_curr_in;
capture_data->status.video_wait.unlock();
capture_data->status.audio_wait.unlock();
}
else {
capture_data->status.cooldown_curr_in = FIX_PARTIAL_FIRST_FRAME_NUM;
default_sleep(20);
}
capture_data->status.curr_delay = last_frame_counter % FRAME_BUFFER_SIZE;
ret = reset_capture_frames(handle, curr_frame_counter, last_frame_counter, single_frame_time, clock_last_reset, curr_capture_type, capture_data->status.capture_type, 1);
if(ret < 0) {
capture_error_print(true, capture_data, "Disconnected: Frame counter reset error");
break;
}
clock_start = curr_time;
capture_data->time_in_buf[inner_curr_in] = diff.count();
capture_data->read[inner_curr_in] = _is_nitro_emulator_get_video_in_size();
inner_curr_in = (inner_curr_in + 1) % NUM_CONCURRENT_DATA_BUFFERS;
if(capture_data->status.cooldown_curr_in)
capture_data->status.cooldown_curr_in = capture_data->status.cooldown_curr_in - 1;
capture_data->status.curr_in = inner_curr_in;
capture_data->status.video_wait.unlock();
capture_data->status.audio_wait.unlock();
}
EndCapture(handle, true, frame_counter);
EndCapture(handle, true, curr_frame_counter, curr_capture_type);
}
void usb_is_nitro_capture_cleanup(CaptureData* capture_data) {
@ -322,16 +449,23 @@ void usb_is_nitro_capture_cleanup(CaptureData* capture_data) {
is_nitro_connection_end((libusb_device_handle*)capture_data->handle);
}
void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureDevice* capture_device) {
void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureScreensType capture_type) {
if(!usb_is_initialized())
return;
for(int i = 0; i < IN_VIDEO_HEIGHT_DS; i++)
for(int j = 0; j < IN_VIDEO_WIDTH_DS; j++) {
int pixel = (i * IN_VIDEO_WIDTH_DS) + j;
p_out->screen_data[pixel][0] = p_in->is_nitro_capture_received.video_in.screen_data[pixel][2];
p_out->screen_data[pixel][1] = p_in->is_nitro_capture_received.video_in.screen_data[pixel][1];
p_out->screen_data[pixel][2] = p_in->is_nitro_capture_received.video_in.screen_data[pixel][0];
}
int num_pixels = _is_nitro_emulator_get_video_in_size(capture_type) / 3;
int out_start_pos = 0;
int out_clear_pos = num_pixels;
if(capture_type == CAPTURE_SCREENS_BOTTOM) {
out_start_pos = num_pixels;
out_clear_pos = 0;
}
if((capture_type == CAPTURE_SCREENS_BOTTOM) || (capture_type == CAPTURE_SCREENS_TOP))
memset(p_out->screen_data[out_clear_pos], 0, num_pixels * 3);
for(int i = 0; i < num_pixels; i++) {
p_out->screen_data[i + out_start_pos][0] = p_in->is_nitro_capture_received.video_in.screen_data[i][2];
p_out->screen_data[i + out_start_pos][1] = p_in->is_nitro_capture_received.video_in.screen_data[i][1];
p_out->screen_data[i + out_start_pos][2] = p_in->is_nitro_capture_received.video_in.screen_data[i][0];
}
}
void usb_is_nitro_init() {