diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f11606..95c0a03 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -525,7 +525,7 @@ if(MSVC) else() set_source_files_properties(source/conversions.cpp PROPERTIES COMPILE_OPTIONS "$<$:-O3>") endif() -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/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/VideoEffectsMenu.cpp source/CaptureDataBuffers.cpp source/Menus/InputMenu.cpp source/Menus/AudioDeviceMenu.cpp source/Menus/SeparatorMenu.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp ${TOOLS_DATA_DIR}/shaders_list.cpp ${SOURCE_CPP_EXTRA_FILES}) +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/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/VideoEffectsMenu.cpp source/CaptureDataBuffers.cpp source/Menus/InputMenu.cpp source/Menus/AudioDeviceMenu.cpp source/Menus/SeparatorMenu.cpp source/Menus/ColorCorrectionMenu.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp ${TOOLS_DATA_DIR}/shaders_list.cpp ${SOURCE_CPP_EXTRA_FILES}) if(NOT ("${EXTRA_DEPENDENCIES}" STREQUAL "")) @@ -591,7 +591,7 @@ add_custom_command( ) set(SHADERS_LIST "") -list(APPEND SHADERS_LIST ${CMAKE_SOURCE_DIR}/shaders/bit_crusher_fragment_shader.2_to_x_1_7.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_crusher_fragment_shader.2_to_x_1_6.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_crusher_fragment_shader_7.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_fragment_shader.2_to_x_0_6.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_fragment_shader_7.frag ${CMAKE_SOURCE_DIR}/shaders/frame_blending_bit_crusher_fragment_shader.2_to_x_1_7.frag ${CMAKE_SOURCE_DIR}/shaders/frame_blending_fragment_shader.frag ${CMAKE_SOURCE_DIR}/shaders/no_effect_fragment_shader.frag) +list(APPEND SHADERS_LIST ${CMAKE_SOURCE_DIR}/shaders/bit_crusher_fragment_shader.2_to_x_1_7.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_crusher_fragment_shader.2_to_x_1_6.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_crusher_fragment_shader_7.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_fragment_shader.2_to_x_0_6.frag ${CMAKE_SOURCE_DIR}/shaders/bit_merger_fragment_shader_7.frag ${CMAKE_SOURCE_DIR}/shaders/frame_blending_bit_crusher_fragment_shader.2_to_x_1_7.frag ${CMAKE_SOURCE_DIR}/shaders/frame_blending_fragment_shader.frag ${CMAKE_SOURCE_DIR}/shaders/no_effect_fragment_shader.frag ${CMAKE_SOURCE_DIR}/shaders/color_emulation_fragment_shader.frag) add_custom_command( OUTPUT ${TOOLS_DATA_DIR}/shaders_list.cpp diff --git a/include/Menus/ColorCorrectionMenu.hpp b/include/Menus/ColorCorrectionMenu.hpp new file mode 100644 index 0000000..877ee56 --- /dev/null +++ b/include/Menus/ColorCorrectionMenu.hpp @@ -0,0 +1,32 @@ +#ifndef __COLORCORRECTIONMENU_HPP +#define __COLORCORRECTIONMENU_HPP + +#include "OptionSelectionMenu.hpp" +#include + +#include "TextRectangle.hpp" +#include "sfml_gfx_structs.hpp" +#include "display_structs.hpp" + +#define COLORCORRECTION_MENU_NO_ACTION -1 +#define COLORCORRECTION_MENU_BACK -2 + +class ColorCorrectionMenu : public OptionSelectionMenu { +public: + ColorCorrectionMenu(bool font_load_success, sf::Font &text_font); + ~ColorCorrectionMenu(); + void setup_title(std::string added_name); + void prepare(float scaling_factor, int view_size_x, int view_size_y, int current_crop); + void insert_data(std::vector* possible_correction_data); + int selected_index = COLORCORRECTION_MENU_NO_ACTION; + void reset_output_option(); +protected: + 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: + const std::string base_name = "Color Correction"; + std::vector* possible_correction_data; +}; +#endif diff --git a/include/Menus/VideoEffectsMenu.hpp b/include/Menus/VideoEffectsMenu.hpp index 47d20fb..3b3793c 100644 --- a/include/Menus/VideoEffectsMenu.hpp +++ b/include/Menus/VideoEffectsMenu.hpp @@ -15,6 +15,7 @@ enum VideoEffectsMenuOutAction{ VIDEO_EFFECTS_MENU_INPUT_COLORSPACE_DEC, VIDEO_EFFECTS_MENU_FRAME_BLENDING_INC, VIDEO_EFFECTS_MENU_FRAME_BLENDING_DEC, + VIDEO_EFFECTS_MENU_COLOR_CORRECTION_MENU, }; class VideoEffectsMenu : public OptionSelectionMenu { diff --git a/include/display_structs.hpp b/include/display_structs.hpp index ea6d933..1e523b9 100755 --- a/include/display_structs.hpp +++ b/include/display_structs.hpp @@ -25,7 +25,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, ISN_MENU_TYPE, VIDEO_EFFECTS_MENU_TYPE, INPUT_MENU_TYPE, AUDIO_DEVICE_MENU_TYPE, SEPARATOR_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, VIDEO_EFFECTS_MENU_TYPE, INPUT_MENU_TYPE, AUDIO_DEVICE_MENU_TYPE, SEPARATOR_MENU_TYPE, COLOR_CORRECTION_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 }; @@ -70,6 +70,8 @@ struct ScreenInfo { bool use_non_integer_scaling_bottom; bool failed_fullscreen; bool have_titlebar; + int top_color_correction; + int bot_color_correction; InputColorspaceMode in_colorspace_top; InputColorspaceMode in_colorspace_bot; FrameBlendingMode frame_blending_top; @@ -133,4 +135,13 @@ struct PARData { std::string name; }; +struct ShaderColorEmulationData { + float targetGamma; + float lum; + float rgb_mod[3][3]; + float displayGamma; + bool is_valid; + std::string name; +}; + #endif diff --git a/include/frontend.hpp b/include/frontend.hpp index 934abf3..f4cd5aa 100755 --- a/include/frontend.hpp +++ b/include/frontend.hpp @@ -35,6 +35,7 @@ #include "InputMenu.hpp" #include "AudioDeviceMenu.hpp" #include "SeparatorMenu.hpp" +#include "ColorCorrectionMenu.hpp" #include "display_structs.hpp" #include "event_structs.hpp" #include "shaders_list.hpp" @@ -81,6 +82,7 @@ public: int get_ret_val(); private: + enum PossibleShaderTypes { BASE_INPUT_SHADER_TYPE, BASE_FINAL_OUTPUT_SHADER_TYPE, COLOR_PROCESSING_SHADER_TYPE }; struct ScreenOperations { bool call_create; bool call_close; @@ -157,11 +159,13 @@ private: InputMenu *input_menu; AudioDeviceMenu *audio_device_menu; SeparatorMenu *separator_menu; + ColorCorrectionMenu *color_correction_menu; std::vector possible_crops; std::vector possible_crops_ds; std::vector possible_crops_with_games; std::vector possible_crops_ds_with_games; std::vector possible_pars; + std::vector possible_color_profiles; std::vector possible_resolutions; std::vector possible_audio_devices; std::vector possible_files; @@ -192,6 +196,8 @@ private: out_rect_data m_out_rect_top, m_out_rect_bot; ScreenType m_stype; + const ShaderColorEmulationData* sent_shader_color_data; + std::queue events_queue; sf::View m_view; @@ -257,6 +263,7 @@ private: void separator_multiplier_change(bool positive, float& multiplier_to_check, float lower_limit, float upper_limit); void separator_windowed_multiplier_change(bool positive); void separator_fullscreen_multiplier_change(bool positive); + void color_correction_value_change(int new_color_correction_value); bool query_reset_request(); void reset_held_times(bool force = true); void poll_window(bool do_everything); @@ -267,8 +274,11 @@ private: bool window_needs_work(); void window_factory(bool is_main_thread); void update_texture(); - int _choose_shader(bool is_input, bool is_top); - int choose_shader(bool is_input, bool is_top); + int _choose_base_input_shader(bool is_top); + int _choose_color_emulation_shader(bool is_top); + int _choose_shader(PossibleShaderTypes shader_type, bool is_top); + int choose_shader(PossibleShaderTypes shader_type, bool is_top); + bool apply_shaders_to_input(out_rect_data &rect_data, const sf::RectangleShape &final_in_rect, bool is_top); void pre_texture_conversion_processing(); void post_texture_conversion_processing(out_rect_data &rect_data, const sf::RectangleShape &in_rect, bool actually_draw, bool is_top, bool is_debug); void draw_rect_to_window(const sf::RectangleShape &out_rect, bool is_top); @@ -327,6 +337,7 @@ private: void setup_video_effects_menu(bool reset_data = true); void setup_input_menu(bool reset_data = true); void setup_separator_menu(bool reset_data = true); + void setup_color_correction_menu(bool reset_data = true); void update_connection(); }; @@ -344,6 +355,7 @@ void FPSArrayDestroy(FPSArray *array); void FPSArrayInsertElement(FPSArray *array, double frame_time); void insert_basic_crops(std::vector &crop_vector, ScreenType s_type, bool is_ds, bool allow_game_specific); void insert_basic_pars(std::vector &par_vector); +void insert_basic_color_profiles(std::vector &color_profiles_vector); void reset_display_data(DisplayData *display_data); void reset_input_data(InputData* input_data); void reset_shared_data(SharedData* shared_data); diff --git a/include/shaders_list.hpp b/include/shaders_list.hpp index bcaf4f9..8c8461b 100644 --- a/include/shaders_list.hpp +++ b/include/shaders_list.hpp @@ -35,6 +35,7 @@ enum shader_list_enum { FRAME_BLENDING_BIT_CRUSHER_FRAGMENT_SHADER_5, FRAME_BLENDING_BIT_CRUSHER_FRAGMENT_SHADER_6, FRAME_BLENDING_BIT_CRUSHER_FRAGMENT_SHADER_7, + COLOR_EMULATION_FRAGMENT_SHADER, TOTAL_NUM_SHADERS }; diff --git a/shaders/color_emulation_fragment_shader.frag b/shaders/color_emulation_fragment_shader.frag new file mode 100644 index 0000000..35ca0d9 --- /dev/null +++ b/shaders/color_emulation_fragment_shader.frag @@ -0,0 +1,33 @@ +uniform sampler2D Texture0; +uniform float targetContrast; +uniform float targetBrightness; +uniform float targetLuminance; +uniform float targetGamma; +uniform float displayGamma; +uniform mat3 color_corr_mat; +uniform mat3 saturation_mat; + +void main() { + gl_FragColor = texture2D(Texture0, gl_TexCoord[0].xy); + vec3 colors_to_edit = gl_FragColor.rgb; + vec3 tatget_brightness_vec = vec3(targetBrightness); + vec3 tatget_gamma_vec = vec3(targetGamma); + vec3 display_gamma_vec = vec3(displayGamma); + + colors_to_edit += tatget_brightness_vec; + colors_to_edit = pow(colors_to_edit, tatget_gamma_vec); + + colors_to_edit *= targetLuminance; + colors_to_edit = clamp(colors_to_edit, 0.0, 1.0); + + colors_to_edit = color_corr_mat * colors_to_edit; + colors_to_edit = saturation_mat * colors_to_edit; + + colors_to_edit = clamp(colors_to_edit, 0.0, 1.0); + + colors_to_edit *= targetContrast; + colors_to_edit = pow(colors_to_edit, display_gamma_vec); + + colors_to_edit = clamp(colors_to_edit, 0.0, 1.0); + gl_FragColor.rgb = colors_to_edit; +} diff --git a/source/Menus/ColorCorrectionMenu.cpp b/source/Menus/ColorCorrectionMenu.cpp new file mode 100644 index 0000000..3f393e0 --- /dev/null +++ b/source/Menus/ColorCorrectionMenu.cpp @@ -0,0 +1,70 @@ +#include "ColorCorrectionMenu.hpp" + +ColorCorrectionMenu::ColorCorrectionMenu(bool font_load_success, sf::Font &text_font) : OptionSelectionMenu(){ + this->initialize(font_load_success, text_font); +} + +ColorCorrectionMenu::~ColorCorrectionMenu() { +} + +void ColorCorrectionMenu::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 = this->base_name; + this->show_back_x = true; + this->show_x = false; + this->show_title = true; +} + +void ColorCorrectionMenu::setup_title(std::string added_name) { + this->title = added_name + (added_name != "" ? " " : "") + this->base_name; +} + +void ColorCorrectionMenu::insert_data(std::vector* possible_correction_data) { + this->possible_correction_data = possible_correction_data; + this->prepare_options(); +} + +void ColorCorrectionMenu::reset_output_option() { + this->selected_index = COLORCORRECTION_MENU_NO_ACTION; +} + +void ColorCorrectionMenu::set_output_option(int index, int action) { + if(index == BACK_X_OUTPUT_OPTION) + this->selected_index = COLORCORRECTION_MENU_BACK; + else + this->selected_index = index; +} + +int ColorCorrectionMenu::get_num_options() { + return (*this->possible_correction_data).size(); +} + +std::string ColorCorrectionMenu::get_string_option(int index, int action) { + return (*this->possible_correction_data)[index]->name; +} + +void ColorCorrectionMenu::prepare(float menu_scaling_factor, int view_size_x, int view_size_y, int current_corr) { + 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 corr_index = start + i; + if(corr_index == current_corr) + this->labels[index]->setText("<" + this->get_string_option(corr_index, DEFAULT_ACTION) + ">"); + else + this->labels[index]->setText(this->get_string_option(corr_index, DEFAULT_ACTION)); + } + this->base_prepare(menu_scaling_factor, view_size_x, view_size_y); +} diff --git a/source/Menus/VideoEffectsMenu.cpp b/source/Menus/VideoEffectsMenu.cpp index b154a90..4171e9b 100644 --- a/source/Menus/VideoEffectsMenu.cpp +++ b/source/Menus/VideoEffectsMenu.cpp @@ -24,9 +24,15 @@ static const VideoEffectsMenuOptionInfo frame_blending_option = { .is_inc = true, .dec_str = "<", .inc_str = ">", .inc_out_action = VIDEO_EFFECTS_MENU_FRAME_BLENDING_INC, .out_action = VIDEO_EFFECTS_MENU_FRAME_BLENDING_DEC}; +static const VideoEffectsMenuOptionInfo color_correction_menu_option = { +.base_name = "Color Correction", .false_name = "", .is_selectable = true, +.is_inc = false, .dec_str = "", .inc_str = "", .inc_out_action = VIDEO_EFFECTS_MENU_NO_ACTION, +.out_action = VIDEO_EFFECTS_MENU_COLOR_CORRECTION_MENU}; + static const VideoEffectsMenuOptionInfo* pollable_options[] = { &input_colorspace_option, &frame_blending_option, +&color_correction_menu_option, }; VideoEffectsMenu::VideoEffectsMenu(bool font_load_success, sf::Font &text_font) : OptionSelectionMenu(){ diff --git a/source/WindowScreen.cpp b/source/WindowScreen.cpp index c984c94..86e014a 100755 --- a/source/WindowScreen.cpp +++ b/source/WindowScreen.cpp @@ -3,6 +3,7 @@ #define GL_SILENCE_DEPRECATION #include #include +#include #include "font_ttf.h" #include "shaders_list.hpp" #include "devicecapture.hpp" @@ -43,6 +44,8 @@ WindowScreen::WindowScreen(ScreenType stype, CaptureStatus* capture_status, Disp insert_basic_crops(this->possible_crops_with_games, this->m_stype, false, true); insert_basic_crops(this->possible_crops_ds_with_games, this->m_stype, true, true); insert_basic_pars(this->possible_pars); + insert_basic_color_profiles(this->possible_color_profiles); + this->sent_shader_color_data = NULL; this->m_prepare_save = 0; this->m_prepare_load = 0; this->m_prepare_open = false; @@ -510,13 +513,62 @@ void WindowScreen::pre_texture_conversion_processing() { this->draw_lock->unlock(); } -int WindowScreen::_choose_shader(bool is_input, bool is_top) { - if(!is_input) - return NO_EFFECT_FRAGMENT_SHADER; - if(this->was_last_frame_null) { - this->was_last_frame_null = false; - return NO_EFFECT_FRAGMENT_SHADER; - } +static void transpose_matrix_to_new_one(float target[3][3], const float source[3][3]) { + for(int i = 0; i < 3; i++) + for(int j = 0; j < 3; j++) + target[i][j] = source[j][i]; +} + +int WindowScreen::_choose_color_emulation_shader(bool is_top) { + int color_profile_index = this->loaded_info.top_color_correction; + if(!is_top) + color_profile_index = this->loaded_info.bot_color_correction; + if((color_profile_index >= possible_color_profiles.size()) || (color_profile_index < 0)) + color_profile_index = 0; + + const ShaderColorEmulationData* shader_color_data = possible_color_profiles[color_profile_index]; + + if(!shader_color_data->is_valid) + return -1; + + int shader_index = COLOR_EMULATION_FRAGMENT_SHADER; + if(sent_shader_color_data == shader_color_data) + return shader_index; + + const float contrast = 1.0; + const float brightness = 0.0; + const float saturation = 1.0; + // Calculate saturation weights. + // Note: We are using the Rec. 709 luminance vector here. + // From Open AGB Firm, thanks to profi200 + const float rwgt = (1.f - saturation) * 0.2126f; + const float gwgt = (1.f - saturation) * 0.7152f; + const float bwgt = (1.f - saturation) * 0.0722f; + float saturation_matrix[3][3] = { + {rwgt + saturation, gwgt, bwgt}, + {rwgt, gwgt + saturation, bwgt}, + {rwgt, gwgt, bwgt + saturation} + }; + // OpenGL is column major... Do the transpose of the matrixes + float saturation_matrix_transposed[3][3]; + float color_corr_matrix_transposed[3][3]; + transpose_matrix_to_new_one(color_corr_matrix_transposed, shader_color_data->rgb_mod); + transpose_matrix_to_new_one(saturation_matrix_transposed, saturation_matrix); + + usable_shaders[shader_index].shader.setUniform("targetContrast", (float)(pow(contrast, shader_color_data->targetGamma))); + usable_shaders[shader_index].shader.setUniform("targetBrightness", brightness / contrast); + usable_shaders[shader_index].shader.setUniform("targetLuminance", shader_color_data->lum); + usable_shaders[shader_index].shader.setUniform("targetGamma", shader_color_data->targetGamma); + usable_shaders[shader_index].shader.setUniform("displayGamma", shader_color_data->displayGamma); + usable_shaders[shader_index].shader.setUniform("color_corr_mat", +sf::Glsl::Mat3((float*)color_corr_matrix_transposed)); + usable_shaders[shader_index].shader.setUniform("saturation_mat", sf::Glsl::Mat3((float*)saturation_matrix_transposed)); + + sent_shader_color_data = shader_color_data; + return shader_index; +} + +int WindowScreen::_choose_base_input_shader(bool is_top) { InputColorspaceMode *in_colorspace = &this->loaded_info.in_colorspace_top; FrameBlendingMode *frame_blending = &this->loaded_info.frame_blending_top; if(!is_top) { @@ -558,13 +610,60 @@ int WindowScreen::_choose_shader(bool is_input, bool is_top) { } } -int WindowScreen::choose_shader(bool is_input, bool is_top) { - int chosen_shader = _choose_shader(is_input, is_top); +int WindowScreen::_choose_shader(PossibleShaderTypes shader_type, bool is_top) { + if(this->was_last_frame_null) { + this->was_last_frame_null = false; + return NO_EFFECT_FRAGMENT_SHADER; + } + switch(shader_type) { + case BASE_FINAL_OUTPUT_SHADER_TYPE: + return NO_EFFECT_FRAGMENT_SHADER; + case BASE_INPUT_SHADER_TYPE: + return _choose_base_input_shader(is_top); + case COLOR_PROCESSING_SHADER_TYPE: + return _choose_color_emulation_shader(is_top); + default: + return NO_EFFECT_FRAGMENT_SHADER; + } +} + +int WindowScreen::choose_shader(PossibleShaderTypes shader_type, bool is_top) { + int chosen_shader = _choose_shader(shader_type, is_top); if((chosen_shader >= 0) && (chosen_shader < usable_shaders.size()) && usable_shaders[chosen_shader].is_valid) return chosen_shader; return -1; } +bool WindowScreen::apply_shaders_to_input(out_rect_data &rect_data, const sf::RectangleShape &final_in_rect, bool is_top) { + if(!sf::Shader::isAvailable()) + return false; + + int chosen_shader = choose_shader(BASE_INPUT_SHADER_TYPE, is_top); + if(chosen_shader < 0) + return false; + + float old_frame_pos_x = ((float)(NUM_FRAMES_BLENDED - 1)) / NUM_FRAMES_BLENDED; + if(this->curr_frame_texture_pos > 0) + old_frame_pos_x = -1.0 / NUM_FRAMES_BLENDED; + sf::Glsl::Vec2 old_pos = {old_frame_pos_x, 0.0}; + usable_shaders[chosen_shader].shader.setUniform("old_frame_offset", old_pos); + rect_data.out_tex.draw(final_in_rect, &usable_shaders[chosen_shader].shader); + + chosen_shader = choose_shader(COLOR_PROCESSING_SHADER_TYPE, is_top); + if(chosen_shader < 0) + return true; + sf::RectangleShape new_final_in_rect = final_in_rect; + new_final_in_rect.setTexture(&rect_data.out_tex.getTexture()); + const sf::IntRect texture_rect = rect_data.out_rect.getTextureRect(); + new_final_in_rect.setTextureRect(texture_rect); + new_final_in_rect.setSize({(float)texture_rect.size.x, (float)texture_rect.size.y}); + new_final_in_rect.setPosition({(float)texture_rect.position.x, (float)texture_rect.position.y}); + new_final_in_rect.setOrigin({0, 0}); + rect_data.out_tex.draw(new_final_in_rect, &usable_shaders[chosen_shader].shader); + return true; + +} + void WindowScreen::post_texture_conversion_processing(out_rect_data &rect_data, const sf::RectangleShape &in_rect, bool actually_draw, bool is_top, bool is_debug) { if((is_top && this->m_stype == ScreenType::BOTTOM) || ((!is_top) && this->m_stype == ScreenType::TOP)) return; @@ -584,19 +683,7 @@ void WindowScreen::post_texture_conversion_processing(out_rect_data &rect_data, text_coords_rect.position.x += this->curr_frame_texture_pos * MAX_IN_VIDEO_WIDTH; final_in_rect.setTextureRect(text_coords_rect); if(this->capture_status->connected && actually_draw) { - bool use_default_shader = true; - if(sf::Shader::isAvailable()) { - int chosen_shader = choose_shader(true, is_top); - if(chosen_shader >= 0) { - float old_frame_pos_x = ((float)(NUM_FRAMES_BLENDED - 1)) / NUM_FRAMES_BLENDED; - if(this->curr_frame_texture_pos > 0) - old_frame_pos_x = -1.0 / NUM_FRAMES_BLENDED; - sf::Glsl::Vec2 old_pos = {old_frame_pos_x, 0.0}; - usable_shaders[chosen_shader].shader.setUniform("old_frame_offset", old_pos); - rect_data.out_tex.draw(final_in_rect, &usable_shaders[chosen_shader].shader); - use_default_shader = false; - } - } + bool use_default_shader = !(this->apply_shaders_to_input(rect_data, final_in_rect, is_top)); if(use_default_shader) rect_data.out_tex.draw(final_in_rect); //Place postprocessing effects here @@ -613,7 +700,7 @@ void WindowScreen::draw_rect_to_window(const sf::RectangleShape &out_rect, bool bool use_default_shader = true; if(sf::Shader::isAvailable()) { - int chosen_shader = choose_shader(false, is_top); + int chosen_shader = choose_shader(BASE_FINAL_OUTPUT_SHADER_TYPE, is_top); if(chosen_shader >= 0) { this->m_win.draw(out_rect, &usable_shaders[chosen_shader].shader); use_default_shader = false; diff --git a/source/WindowScreen_Menu.cpp b/source/WindowScreen_Menu.cpp index 7b991c2..1cc55ad 100755 --- a/source/WindowScreen_Menu.cpp +++ b/source/WindowScreen_Menu.cpp @@ -119,6 +119,7 @@ void WindowScreen::init_menus() { this->input_menu = new InputMenu(this->font_load_success, this->text_font); this->audio_device_menu = new AudioDeviceMenu(this->font_load_success, this->text_font); this->separator_menu = new SeparatorMenu(this->font_load_success, this->text_font); + this->color_correction_menu = new ColorCorrectionMenu(this->font_load_success, this->text_font); } void WindowScreen::destroy_menus() { @@ -145,6 +146,7 @@ void WindowScreen::destroy_menus() { delete this->input_menu; delete this->audio_device_menu; delete this->separator_menu; + delete this->color_correction_menu; } void WindowScreen::set_close(int ret_val) { @@ -299,6 +301,22 @@ void WindowScreen::par_value_change(int new_par_value, bool is_top) { } } +void WindowScreen::color_correction_value_change(int new_color_correction_value) { + int color_correction_index = new_color_correction_value % this->possible_color_profiles.size(); + if(color_correction_index < 0) + color_correction_index = 0; + std::string setting_name = "Color Correction: "; + bool updated = false; + if(this->m_info.top_color_correction != color_correction_index) + updated = true; + this->m_info.top_color_correction = color_correction_index; + if(this->m_info.bot_color_correction != color_correction_index) + updated = true; + this->m_info.bot_color_correction = color_correction_index; + if(updated) + this->print_notification(setting_name + this->possible_color_profiles[color_correction_index]->name); +} + void WindowScreen::offset_change(float &value, float change) { if(change >= 1.0) return; @@ -1115,6 +1133,18 @@ void WindowScreen::setup_separator_menu(bool reset_data) { } } +void WindowScreen::setup_color_correction_menu(bool reset_data) { + if(!this->can_setup_menu()) + return; + if(this->curr_menu != COLOR_CORRECTION_MENU_TYPE) { + this->curr_menu = COLOR_CORRECTION_MENU_TYPE; + if(reset_data) + this->color_correction_menu->reset_data(); + this->color_correction_menu->insert_data(&this->possible_color_profiles); + 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; @@ -2073,6 +2103,10 @@ void WindowScreen::poll(bool do_everything) { case VIDEO_EFFECTS_MENU_FRAME_BLENDING_DEC: this->frame_blending_mode_change(false); break; + case VIDEO_EFFECTS_MENU_COLOR_CORRECTION_MENU: + this->setup_color_correction_menu(); + done = true; + break; default: break; } @@ -2178,6 +2212,23 @@ void WindowScreen::poll(bool do_everything) { continue; } break; + case COLOR_CORRECTION_MENU_TYPE: + if(this->color_correction_menu->poll(event_data)) { + switch(this->color_correction_menu->selected_index) { + case COLORCORRECTION_MENU_BACK: + this->setup_video_effects_menu(false); + done = true; + break; + case COLORCORRECTION_MENU_NO_ACTION: + break; + default: + this->color_correction_value_change(this->color_correction_menu->selected_index); + break; + } + this->color_correction_menu->reset_output_option(); + continue; + } + break; default: break; } @@ -2457,6 +2508,9 @@ void WindowScreen::prepare_menu_draws(int view_size_x, int view_size_y) { case SEPARATOR_MENU_TYPE: this->separator_menu->prepare(this->loaded_info.menu_scaling_factor, view_size_x, view_size_y, &this->loaded_info); break; + case COLOR_CORRECTION_MENU_TYPE: + this->color_correction_menu->prepare(this->loaded_info.menu_scaling_factor, view_size_x, view_size_y, this->loaded_info.top_color_correction); + break; default: break; } @@ -2539,6 +2593,9 @@ void WindowScreen::execute_menu_draws() { case SEPARATOR_MENU_TYPE: this->separator_menu->draw(this->loaded_info.menu_scaling_factor, this->m_win); break; + case COLOR_CORRECTION_MENU_TYPE: + this->color_correction_menu->draw(this->loaded_info.menu_scaling_factor, this->m_win); + break; default: break; } diff --git a/source/frontend.cpp b/source/frontend.cpp index a7b849b..bef8c54 100755 --- a/source/frontend.cpp +++ b/source/frontend.cpp @@ -363,6 +363,142 @@ static const PARData* basic_possible_pars[] = { &fit_wide_vertical_par, }; +// libretro shader values. Credits: hunterk and Pokefan531. +// Based on profi200's open_agb_firm implementation. +// https://github.com/libretro/slang-shaders/tree/master/handheld/shaders/color +const ShaderColorEmulationData color_profile_identity = { +.targetGamma = 1.f, .lum = 1.f, +.rgb_mod = { +{1.f, 0.f, 0.f}, +{0.f, 1.f, 0.f}, +{0.f, 0.f, 1.f} +}, +.displayGamma = 1.f / 1.f, +.is_valid = false, +.name = "No Correction" +}; + +const ShaderColorEmulationData color_profile_libretro_gba = { +.targetGamma = 2.2f, .lum = 0.91f, +.rgb_mod = { +{0.905f, 0.195f, -0.1f}, +{0.1f, 0.65f, 0.25f}, +{0.1575f, 0.1425f, 0.7f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "GBA" +}; + +const ShaderColorEmulationData color_profile_libretro_gb_micro = { +.targetGamma = 2.2f, .lum = 0.9f, +.rgb_mod = { +{0.8025f, 0.31f, -0.1125f}, +{0.1f, 0.6875f, 0.2125f}, +{0.1225f, 0.1125f, 0.765f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "GB Micro" +}; + +const ShaderColorEmulationData color_profile_libretro_gba_sp_101 = { +.targetGamma = 2.2f, .lum = 0.935f, +.rgb_mod = { +{0.96f, 0.11f, -0.07f}, +{0.0325f, 0.89f, 0.0775f}, +{0.001f, -0.03f, 1.029f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "GBA SP 101" +}; + +const ShaderColorEmulationData color_profile_libretro_nds = { +.targetGamma = 2.2f, .lum = 0.905f, +.rgb_mod = { +{0.835f, 0.27f, -0.105f}, +{0.1f, 0.6375f, 0.2625f}, +{0.105f, 0.175f, 0.72f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "Nintendo DS" +}; + +const ShaderColorEmulationData color_profile_libretro_nds_lite = { +.targetGamma = 2.2f, .lum = 0.935f, +.rgb_mod = { +{0.93f, 0.14f, -0.07f}, +{0.025f, 0.9f, 0.075f}, +{0.008f, -0.03f, 1.022f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "Nintendo DS Lite" +}; + +const ShaderColorEmulationData color_profile_libretro_nso_gba = { +.targetGamma = 2.2f, .lum = 1.f, +.rgb_mod = { +{0.865f, 0.1225f, 0.0125f}, +{0.0575f, 0.925f, 0.0125f}, +{0.0575f, 0.1225f, 0.82f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "NSO GBA" +}; + +const ShaderColorEmulationData color_profile_libretro_emulators_gba = { +.targetGamma = 1.45f, .lum = 1.f, +.rgb_mod = { +{0.73f, 0.27f, 0.f}, +{0.0825f, 0.6775f, 0.24f}, +{0.0825f, 0.24f, 0.6775f} +}, +.displayGamma = 1.f / 1.45f, +.is_valid = true, +.name = "Emulators GBA" +}; + +const ShaderColorEmulationData color_profile_libretro_gbc = { +.targetGamma = 2.2f, .lum = 0.91f, +.rgb_mod = { +{0.905f, 0.195f, -0.1f}, +{0.1f, 0.65f, 0.25f}, +{0.1575f, 0.1425f, 0.7f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "GBC" +}; + +const ShaderColorEmulationData color_profile_nso_gbc = { +.targetGamma = 2.2f, .lum = 0.85f, +.rgb_mod = { +{0.84f, 0.265f, 0.f}, +{0.105f, 0.67f, 0.24f}, +{0.15f, 0.30f, 0.525f} +}, +.displayGamma = 1.f / 2.2f, +.is_valid = true, +.name = "NSO GBC" +}; + +static const ShaderColorEmulationData* basic_possible_color_profiles[] = { +&color_profile_identity, +&color_profile_libretro_nds, +&color_profile_libretro_nds_lite, +&color_profile_libretro_gba, +&color_profile_libretro_gb_micro, +&color_profile_libretro_gba_sp_101, +&color_profile_libretro_nso_gba, +&color_profile_libretro_emulators_gba, +&color_profile_libretro_gbc, +&color_profile_nso_gbc, +}; + static bool is_allowed_crop(const CropData* crop_data, ScreenType s_type, bool is_ds, bool allow_game_specific) { if(is_ds && (!crop_data->allowed_ds)) return false; @@ -392,6 +528,12 @@ void insert_basic_pars(std::vector &par_vector) { } } +void insert_basic_color_profiles(std::vector &color_profiles_vector) { + for(int i = 0; i < (sizeof(basic_possible_color_profiles) / sizeof(basic_possible_color_profiles[0])); i++) { + color_profiles_vector.push_back(basic_possible_color_profiles[i]); + } +} + void reset_display_data(DisplayData* display_data) { display_data->last_connected_ds = false; } @@ -461,6 +603,8 @@ void reset_screen_info(ScreenInfo &info) { info.use_non_integer_scaling_bottom = false; info.failed_fullscreen = false; info.have_titlebar = true; + info.top_color_correction = 0; + info.bot_color_correction = 0; info.in_colorspace_top = FULL_COLORSPACE; info.in_colorspace_bot = FULL_COLORSPACE; info.frame_blending_top = NO_FRAME_BLENDING; @@ -684,6 +828,14 @@ bool load_screen_info(std::string key, std::string value, std::string base, Scre info.have_titlebar = std::stoi(value); return true; } + if(key == (base + "top_color_correction")) { + info.top_color_correction = std::stoi(value); + return true; + } + if(key == (base + "bot_color_correction")) { + info.bot_color_correction = std::stoi(value); + return true; + } if(key == (base + "in_colorspace_top")) { info.in_colorspace_top = input_colorspace_sanitization(std::stoi(value)); return true; @@ -740,6 +892,8 @@ std::string save_screen_info(std::string base, const ScreenInfo &info) { out += base + "use_non_integer_scaling_top=" + std::to_string(info.use_non_integer_scaling_top) + "\n"; out += base + "use_non_integer_scaling_bottom=" + std::to_string(info.use_non_integer_scaling_bottom) + "\n"; out += base + "have_titlebar=" + std::to_string(info.have_titlebar) + "\n"; + out += base + "top_color_correction=" + std::to_string(info.top_color_correction) + "\n"; + out += base + "bot_color_correction=" + std::to_string(info.bot_color_correction) + "\n"; out += base + "in_colorspace_top=" + std::to_string(info.in_colorspace_top) + "\n"; out += base + "in_colorspace_bot=" + std::to_string(info.in_colorspace_bot) + "\n"; out += base + "frame_blending_top=" + std::to_string(info.frame_blending_top) + "\n";