diff --git a/Utilities/geometry.h b/Utilities/geometry.h index 3ffbc04dd3..a5881d1cd5 100644 --- a/Utilities/geometry.h +++ b/Utilities/geometry.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -1027,3 +1028,6 @@ using color1u = color1_base; using color1i = color1_base; using color1f = color1_base; using color1d = color1_base; + +using mat3f = color3_base[3]; +static_assert(sizeof(mat3f) == sizeof(float) * 3 * 3); diff --git a/Utilities/stereo_config.cpp b/Utilities/stereo_config.cpp new file mode 100644 index 0000000000..01b1fdfab9 --- /dev/null +++ b/Utilities/stereo_config.cpp @@ -0,0 +1,160 @@ +#include "stdafx.h" +#include "stereo_config.h" +#include "Emu/system_config.h" + +stereo_config::stereo_matrices::stereo_matrices(const std::array, 3>& l, const std::array, 3>& r) +{ + for (usz i = 0; i < 3; i++) + { + left[i] = l[i]; + right[i] = r[i]; + } +} + +atomic_t stereo_config::s_live_preview_enabled = false; +stereo_config::stereo_matrices stereo_config::m_live_matrices = {}; + +const std::unordered_map stereo_config::m_matrices = +{ + {stereo_render_mode_options::anaglyph_red_cyan, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_red_green, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + } + )}, + {stereo_render_mode_options::anaglyph_red_blue, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_magenta_cyan, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0.5f) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0.5f) + } + )}, + {stereo_render_mode_options::anaglyph_trioscopic, stereo_matrices( + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + }, + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_amber_blue, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 1.0f / 3.0f), + color3_base(0, 0, 1.0f / 3.0f), + color3_base(0, 0, 1.0f / 3.0f) + } + )}, +}; + +void stereo_config::update_from_config(bool stereo_enabled) +{ + m_stereo_mode = stereo_enabled ? g_cfg.video.stereo_render_mode.get() : stereo_render_mode_options::disabled; + + if (m_stereo_mode == stereo_render_mode_options::anaglyph_custom) + { + stereo_config::stereo_matrices custom_matrices {}; + stereo_config::convert_matrix(m_custom_matrices.left, g_cfg.video.custom_anaglyph_matrices.left.get_map()); + stereo_config::convert_matrix(m_custom_matrices.right, g_cfg.video.custom_anaglyph_matrices.right.get_map()); + } +} + +const stereo_config::stereo_matrices& stereo_config::matrices() const +{ + if (m_in_emulation && s_live_preview_enabled) + { + return m_live_matrices; + } + + switch (m_stereo_mode) + { + case stereo_render_mode_options::disabled: + case stereo_render_mode_options::side_by_side: + case stereo_render_mode_options::over_under: + case stereo_render_mode_options::interlaced: + break; + case stereo_render_mode_options::anaglyph_red_green: + case stereo_render_mode_options::anaglyph_red_blue: + case stereo_render_mode_options::anaglyph_red_cyan: + case stereo_render_mode_options::anaglyph_magenta_cyan: + case stereo_render_mode_options::anaglyph_trioscopic: + case stereo_render_mode_options::anaglyph_amber_blue: + return ::at32(m_matrices, m_stereo_mode); + case stereo_render_mode_options::anaglyph_custom: + return m_custom_matrices; + } + + static const stereo_matrices s_left_only_matrices = stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 1) + }, + { + color3_base(0, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }); + return s_left_only_matrices; +} + +std::map stereo_config::get_custom_matrix(bool is_left) const +{ + return convert_matrix(is_left ? m_custom_matrices.left : m_custom_matrices.right); +} + +std::map stereo_config::convert_matrix(const mat3f& mat) +{ + std::map values {}; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + values[fmt::format("%d%d", i, j)] = fmt::format("%f", mat[i].rgb[j]); + } + } + return values; +} diff --git a/Utilities/stereo_config.h b/Utilities/stereo_config.h new file mode 100644 index 0000000000..468ce860ab --- /dev/null +++ b/Utilities/stereo_config.h @@ -0,0 +1,77 @@ +#pragma once + +#include "geometry.h" +#include "Emu/system_config_types.h" +#include "Utilities/StrUtil.h" +#include +#include + +struct stereo_config +{ +public: + struct stereo_matrices + { + stereo_matrices() = default; + stereo_matrices(const std::array, 3>& l, const std::array, 3>& r); + + mat3f left {}; + mat3f right {}; + }; + + stereo_config(bool in_emulation) : m_in_emulation(in_emulation) {} + + void set_stereo_mode(stereo_render_mode_options mode) { m_stereo_mode = mode; } + stereo_render_mode_options stereo_mode() const { return m_stereo_mode; } + + const stereo_matrices& matrices() const; + + void update_from_config(bool stereo_enabled); + + void set_custom_matrices(const stereo_matrices& matrices) { m_custom_matrices = matrices; } + static void set_live_matrices(const stereo_matrices& matrices) { m_live_matrices = matrices; } + + std::map get_custom_matrix(bool is_left) const; + + template + static void convert_matrix(mat3f& mat, const T& values) + { + mat[0] = {}; + mat[1] = {}; + mat[2] = {}; + + if (values.empty()) + { + return; + } + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + const std::string key = fmt::format("%d%d", i, j); + const auto it = values.find(key); + if (it == values.cend()) continue; + + const std::string& val_s = it->second; + if (val_s.empty()) continue; + + if (f64 val = 0.0f; try_to_float(&val, val_s, -10.0f, 10.0f)) + { + mat[i].rgb[j] = static_cast(val); + } + } + } + } + + static std::map convert_matrix(const mat3f& mat); + + static atomic_t s_live_preview_enabled; + +private: + stereo_matrices m_custom_matrices {}; + stereo_render_mode_options m_stereo_mode = stereo_render_mode_options::disabled; + bool m_in_emulation = false; + + static stereo_matrices m_live_matrices; + static const std::unordered_map m_matrices; +}; diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 0b7971c07e..621d811207 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -126,7 +126,7 @@ struct PKGEntry struct PKGMetaData { private: - static std::string to_hex_string(u8 buf[], usz size) + static std::string to_hex_string(const u8* buf, usz size) { std::stringstream sstream; for (usz i = 0; i < size; i++) @@ -135,7 +135,7 @@ private: } return sstream.str(); } - static std::string to_hex_string(u8 buf[], usz size, usz dotpos) + static std::string to_hex_string(const u8* buf, usz size, usz dotpos) { std::string result = to_hex_string(buf, size); if (result.size() > dotpos) diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 0de2d08839..77c8348335 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -77,6 +77,7 @@ target_sources(rpcs3_emu PRIVATE ../../Utilities/sema.cpp ../../Utilities/simple_ringbuf.cpp ../../Utilities/stack_trace.cpp + ../../Utilities/stereo_config.cpp ../../Utilities/StrFmt.cpp ../../Utilities/Thread.cpp ../../Utilities/version.cpp diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.cpp b/rpcs3/Emu/RSX/GL/GLOverlays.cpp index a758804e4f..2bf7100ceb 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.cpp +++ b/rpcs3/Emu/RSX/GL/GLOverlays.cpp @@ -1,6 +1,7 @@ #include "GLOverlays.h" #include "Utilities/StrUtil.h" +#include "Utilities/stereo_config.h" #include "../Program/RSXOverlay.h" #include "Emu/Cell/timers.hpp" @@ -510,7 +511,7 @@ namespace gl } void video_out_calibration_pass::run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, gl::filter input_filter) + bool stereo_enabled, gl::filter input_filter) { if (m_input_filter != input_filter) { @@ -519,10 +520,16 @@ namespace gl m_sampler.set_parameteri(GL_TEXTURE_MAG_FILTER, static_cast(m_input_filter)); } + static stereo_config stereo_cfg = stereo_config(true); + stereo_cfg.update_from_config(stereo_enabled); + const auto& matrices = stereo_cfg.matrices(); + program_handle.uniforms["gamma"] = gamma; program_handle.uniforms["limit_range"] = limited_rgb + 0; - program_handle.uniforms["stereo_display_mode"] = stereo_enabled ? static_cast(stereo_mode) : 0; - program_handle.uniforms["stereo_image_count"] = (source[1] == GL_NONE? 1 : 2); + program_handle.uniforms["stereo_display_mode"] = static_cast(stereo_cfg.stereo_mode()); + program_handle.uniforms["stereo_image_count"] = (source[1] == GL_NONE ? 1 : 2); + program_handle.uniforms["left_anaglyph_matrix"] = matrices.left; + program_handle.uniforms["right_anaglyph_matrix"] = matrices.right; saved_sampler_state saved(GL_TEMP_IMAGE_SLOT(0), m_sampler); cmd->bind_texture(GL_TEMP_IMAGE_SLOT(0), GL_TEXTURE_2D, source[0]); diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.h b/rpcs3/Emu/RSX/GL/GLOverlays.h index 8ccfd67305..c10a8ca394 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.h +++ b/rpcs3/Emu/RSX/GL/GLOverlays.h @@ -98,7 +98,7 @@ namespace gl video_out_calibration_pass(); void run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, gl::filter input_filter); + bool stereo_enabled, gl::filter input_filter); }; struct rp_ssbo_to_generic_texture final : public overlay_pass diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index 2aa11868ee..230d16694e 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -436,7 +436,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) } gl::screen.bind(); - m_video_output_pass.run(cmd, areau(aspect_ratio), images.map(FN(x ? x->id() : GL_NONE)), gamma, limited_range, avconfig.stereo_enabled, g_cfg.video.stereo_render_mode, filter); + m_video_output_pass.run(cmd, areau(aspect_ratio), images.map(FN(x ? x->id() : GL_NONE)), gamma, limited_range, avconfig.stereo_enabled, filter); } } diff --git a/rpcs3/Emu/RSX/GL/glutils/program.h b/rpcs3/Emu/RSX/GL/glutils/program.h index 0568bc5547..ea55107c1f 100644 --- a/rpcs3/Emu/RSX/GL/glutils/program.h +++ b/rpcs3/Emu/RSX/GL/glutils/program.h @@ -104,6 +104,7 @@ namespace gl void operator = (const color4f& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.r, rhs.g, rhs.b, rhs.a); } void operator = (const areaf& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } void operator = (const areai& rhs) const { glProgramUniform4i(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } + void operator = (const mat3f& rhs) const { glProgramUniformMatrix3fv(m_program.id(), location(), 1, GL_FALSE, &rhs[0].rgb[0]); } void operator = (const std::span& rhs) const { glProgramUniform1iv(m_program.id(), location(), ::size32(rhs), rhs.data()); } void operator = (const std::span& rhs) const { glProgramUniformHandleui64vARB(m_program.id(), location(), ::size32(rhs), rhs.data()); } }; diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl index f33dbd6aa5..8ebc8ae787 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl @@ -19,6 +19,7 @@ layout(location=0) out vec4 ocol; #define STEREO_MODE_ANAGLYPH_MAGENTA_CYAN 7 #define STEREO_MODE_ANAGLYPH_TRIOSCOPIC 8 #define STEREO_MODE_ANAGLYPH_AMBER_BLUE 9 +#define STEREO_MODE_ANAGLYPH_CUSTOM 10 #define TRUE 1 #define FALSE 0 @@ -37,26 +38,28 @@ layout(push_constant) uniform static_data int limit_range; int stereo_display_mode; int stereo_image_count; + mat3 left_anaglyph_matrix; + mat3 right_anaglyph_matrix; }; #else uniform float gamma; uniform int limit_range; uniform int stereo_display_mode; uniform int stereo_image_count; +uniform mat3 left_anaglyph_matrix; +uniform mat3 right_anaglyph_matrix; #endif +vec3 applyMatrix(vec3 left, vec3 right) +{ + vec3 outColor = left_anaglyph_matrix * left + right_anaglyph_matrix * right; + return clamp(outColor, 0.0, 1.0); +} + vec4 anaglyph(const in vec4 left, const in vec4 right) { - switch (stereo_display_mode) - { - case STEREO_MODE_ANAGLYPH_RED_GREEN: return vec4(left.r, right.g, 0.f, 1.f); - case STEREO_MODE_ANAGLYPH_RED_BLUE: return vec4(left.r, 0.f, right.b, 1.f); - case STEREO_MODE_ANAGLYPH_RED_CYAN: return vec4(left.r, right.g, right.b, 1.f); - case STEREO_MODE_ANAGLYPH_MAGENTA_CYAN: return vec4(left.r, right.g, (left.b + right.b) / 2.f, 1.f); - case STEREO_MODE_ANAGLYPH_TRIOSCOPIC: return vec4(right.r, left.g, right.b, 1.f); - case STEREO_MODE_ANAGLYPH_AMBER_BLUE: return vec4(left.r, left.g, (right.r + right.g + right.b) / 3.f, 1.f); - default: return texture(fs0, tc0); - } + vec3 color = applyMatrix(left.rgb, right.rgb); + return vec4(color, 1.0); } vec4 anaglyph_single_image() @@ -92,6 +95,7 @@ vec4 read_source() case STEREO_MODE_ANAGLYPH_MAGENTA_CYAN: case STEREO_MODE_ANAGLYPH_TRIOSCOPIC: case STEREO_MODE_ANAGLYPH_AMBER_BLUE: + case STEREO_MODE_ANAGLYPH_CUSTOM: return anaglyph_single_image(); case STEREO_MODE_SIDE_BY_SIDE: return (tc0.x < 0.5) @@ -120,6 +124,7 @@ vec4 read_source() case STEREO_MODE_ANAGLYPH_MAGENTA_CYAN: case STEREO_MODE_ANAGLYPH_TRIOSCOPIC: case STEREO_MODE_ANAGLYPH_AMBER_BLUE: + case STEREO_MODE_ANAGLYPH_CUSTOM: return anaglyph_stereo_image(); case STEREO_MODE_SIDE_BY_SIDE: return (tc0.x < 0.5) diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.cpp b/rpcs3/Emu/RSX/VK/VKOverlays.cpp index 684cf8fedb..f879a62ddf 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.cpp +++ b/rpcs3/Emu/RSX/VK/VKOverlays.cpp @@ -14,6 +14,7 @@ #include "../Program/RSXOverlay.h" #include "util/fnv_hash.hpp" +#include "Utilities/stereo_config.h" #include "Emu/Cell/timers.hpp" @@ -911,18 +912,28 @@ namespace vk void video_out_calibration_pass::update_uniforms(vk::command_buffer& cmd, vk::glsl::program* program) { - vkCmdPushConstants(cmd, program->layout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, config.data); + vkCmdPushConstants(cmd, program->layout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(config.data), config.data); } void video_out_calibration_pass::run(vk::command_buffer& cmd, const areau& viewport, vk::framebuffer* target, const rsx::simple_array& src, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, VkRenderPass render_pass) + bool stereo_enabled, VkRenderPass render_pass) { + static stereo_config stereo_cfg = stereo_config(true); + stereo_cfg.update_from_config(stereo_enabled); + const auto& matrices = stereo_cfg.matrices(); + config.gamma = gamma; - config.limit_range = limited_rgb? 1 : 0; - config.stereo_display_mode = stereo_enabled ? static_cast(stereo_mode) : 0; + config.limit_range = limited_rgb ? 1 : 0; + config.stereo_display_mode = static_cast(stereo_cfg.stereo_mode()); config.stereo_image_count = std::min(::size32(src), 2u); + for (u32 i = 0; i < 3; i++) + { + std::memcpy(config.left_anaglyph_matrix[i].rgba, matrices.left[i].rgb, sizeof(matrices.left[i].rgb)); + std::memcpy(config.right_anaglyph_matrix[i].rgba, matrices.right[i].rgb, sizeof(matrices.right[i].rgb)); + } + std::vector views; views.reserve(2); diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.h b/rpcs3/Emu/RSX/VK/VKOverlays.h index de2c218ebe..cfae31f9df 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.h +++ b/rpcs3/Emu/RSX/VK/VKOverlays.h @@ -209,9 +209,18 @@ namespace vk int limit_range; int stereo_display_mode; int stereo_image_count; + color4_base left_anaglyph_matrix[3]; + color4_base right_anaglyph_matrix[3]; }; - float data[4]; + float data[( + sizeof(gamma) + + sizeof(limit_range) + + sizeof(stereo_display_mode) + + sizeof(stereo_image_count) + + sizeof(left_anaglyph_matrix) + + sizeof(right_anaglyph_matrix) + ) / sizeof(float)]; } config = {}; @@ -223,7 +232,7 @@ namespace vk void run(vk::command_buffer& cmd, const areau& viewport, vk::framebuffer* target, const rsx::simple_array& src, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, VkRenderPass render_pass); + bool stereo_enabled, VkRenderPass render_pass); }; // TODO: Replace with a proper manager diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 432e8c7e87..b5ba9c4607 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -839,7 +839,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) vk::get_overlay_pass()->run( *m_current_command_buffer, areau(aspect_ratio), direct_fbo, calibration_src, - avconfig.gamma, !use_full_rgb_range_output, avconfig.stereo_enabled, g_cfg.video.stereo_render_mode, single_target_pass); + avconfig.gamma, !use_full_rgb_range_output, avconfig.stereo_enabled, single_target_pass); direct_fbo->release(); } diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 1272896f75..e10c12610a 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -234,6 +234,15 @@ struct cfg_root : cfg::node } shader_preloading_dialog{ this }; + struct anaglyph_matrices : cfg::node + { + anaglyph_matrices(cfg::node* _this) : cfg::node(_this, "Custom Anaglyph Matrices") {} + + cfg::node_map_entry left{ this, "Matrix Left" }; + cfg::node_map_entry right{ this, "Matrix Right" }; + + } custom_anaglyph_matrices{ this }; + } video{ this }; struct node_audio : cfg::node diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 436028db90..ccb029d94d 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -681,6 +681,7 @@ void fmt_class_string::format(std::string& out, u64 case stereo_render_mode_options::anaglyph_magenta_cyan: return "Anaglyph Magenta-Cyan"; case stereo_render_mode_options::anaglyph_trioscopic: return "Anaglyph Trioscopic"; case stereo_render_mode_options::anaglyph_amber_blue: return "Anaglyph Amber-Blue"; + case stereo_render_mode_options::anaglyph_custom: return "Anaglyph Custom"; } return unknown; diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index 95cfe72a2a..49c529caa7 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -361,6 +361,7 @@ enum class stereo_render_mode_options anaglyph_magenta_cyan, anaglyph_trioscopic, anaglyph_amber_blue, + anaglyph_custom, }; enum class xfloat_accuracy diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 9a8bc5a76c..31ef781d93 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -59,6 +59,7 @@ + @@ -574,6 +575,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 8534c79285..17ed08a3f6 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1435,6 +1435,9 @@ Loader + + Utilities + @@ -2881,6 +2884,9 @@ Loader + + Utilities + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 8bf5e5bc4f..61f05473b0 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -838,6 +838,7 @@ + @@ -1197,6 +1198,7 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 184069c388..e755ee4a67 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -1311,6 +1311,9 @@ Gui\game list + + Gui\settings + @@ -1562,6 +1565,9 @@ Gui\utils + + Gui\settings + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 849e162a86..160aa4fb05 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(rpcs3_ui STATIC about_dialog.cpp + anaglyph_settings_dialog.cpp auto_pause_settings_dialog.cpp basic_mouse_settings_dialog.cpp breakpoint_handler.cpp diff --git a/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp b/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp new file mode 100644 index 0000000000..14c4fea62b --- /dev/null +++ b/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp @@ -0,0 +1,293 @@ +#include "stdafx.h" +#include "anaglyph_settings_dialog.h" + +#include +#include +#include +#include + +color_wedge_widget::color_wedge_widget(QWidget* parent) + : QWidget(parent) +{ + setObjectName("color_wedge_widget"); + setMinimumSize(300, 300); +} + +color_wedge_widget::~color_wedge_widget() +{ + stereo_config::s_live_preview_enabled = false; +} + +void color_wedge_widget::set_option(stereo_render_mode_options option) +{ + m_stereo_config.set_stereo_mode(option); + update(); +} + +void color_wedge_widget::set_custom_matrices(const stereo_config::stereo_matrices& matrices) +{ + m_stereo_config.set_custom_matrices(matrices); + update(); +} + +QVector3D color_wedge_widget::apply_matrix(const QVector3D& left, const QVector3D& right, const stereo_config::stereo_matrices& m) +{ + const mat3f& ml = m.left; + const mat3f& mr = m.right; + + QVector3D out; + out.setX(ml[0].x * left.x() + ml[1].x * left.y() + ml[2].x * left.z() + + mr[0].x * right.x() + mr[1].x * right.y() + mr[2].x * right.z()); + + out.setY(ml[0].y * left.x() + ml[1].y * left.y() + ml[2].y * left.z() + + mr[0].y * right.x() + mr[1].y * right.y() + mr[2].y * right.z()); + + out.setZ(ml[0].z * left.x() + ml[1].z * left.y() + ml[2].z * left.z() + + mr[0].z * right.x() + mr[1].z * right.y() + mr[2].z * right.z()); + + out.setX(std::clamp(out.x(), 0.0f, 1.0f)); + out.setY(std::clamp(out.y(), 0.0f, 1.0f)); + out.setZ(std::clamp(out.z(), 0.0f, 1.0f)); + + return out; +} + +QVector3D color_wedge_widget::apply_anaglyph_matrix(const QVector3D& left, const QVector3D& right) +{ + if (m_stereo_config.stereo_mode() == stereo_render_mode_options::disabled) + { + return left; + } + + return apply_matrix(left, right, m_stereo_config.matrices()); +} + +void color_wedge_widget::paintEvent(QPaintEvent* /*event*/) +{ + const int w = width(); + const int h = height(); + + if (m_img.width() != w || m_img.height() != h) + { + m_img = QImage(w, h, QImage::Format_RGB32); + } + + QVector3D out; + + // Loop over pixels + for (int y = 0; y < h; ++y) + { + const float v = float(y) / (h - 1); + + for (int x = 0; x < w; ++x) + { + const float u = float(x) / (w - 1); + + const QVector3D left(u, v, 1.0f - u); + const QVector3D right = left; + + const QVector3D out = apply_anaglyph_matrix(left, right); + + m_img.setPixel(x, y, qRgb(int(out.x() * 255), int(out.y() * 255), int(out.z() * 255))); + } + } + + // Paint the image + QPainter p(this); + p.drawImage(0, 0, m_img); +} + +anaglyph_settings_dialog::anaglyph_settings_dialog(QWidget* parent, std::shared_ptr emu_settings) + : QDialog(parent) + , m_emu_settings(std::move(emu_settings)) +{ + setWindowTitle(tr("Anaglyph Settings")); + setObjectName("anaglyph_settings_dialog"); + setAttribute(Qt::WA_DeleteOnClose); + + stereo_render_mode_options current_mode = stereo_render_mode_options::disabled; + if (u64 result {}; cfg::try_to_enum_value(&result, &fmt_class_string::format, m_emu_settings->GetSetting(emu_settings_type::StereoRenderMode))) + { + const auto mode = static_cast(static_cast>(result)); + switch (mode) + { + case stereo_render_mode_options::anaglyph_red_green: + case stereo_render_mode_options::anaglyph_red_blue: + case stereo_render_mode_options::anaglyph_red_cyan: + case stereo_render_mode_options::anaglyph_magenta_cyan: + case stereo_render_mode_options::anaglyph_trioscopic: + case stereo_render_mode_options::anaglyph_amber_blue: + case stereo_render_mode_options::anaglyph_custom: + current_mode = mode; + break; + default: + break; + } + } + + stereo_config::stereo_matrices custom_matrices {}; + stereo_config::convert_matrix(custom_matrices.left, m_emu_settings->GetMapSetting(emu_settings_type::CustomAnaglyphMatrixLeft)); + stereo_config::convert_matrix(custom_matrices.right, m_emu_settings->GetMapSetting(emu_settings_type::CustomAnaglyphMatrixRight)); + + m_widget_reference = new color_wedge_widget(this); + m_widget_reference->set_option(stereo_render_mode_options::disabled); + + m_widget = new color_wedge_widget(this); + m_widget->set_option(current_mode); + m_widget->set_custom_matrices(custom_matrices); + + QPushButton* apply_button = new QPushButton(tr("Apply Custom Matrices")); + connect(apply_button, &QAbstractButton::clicked, this, [this]() + { + m_emu_settings->SetMapSetting(emu_settings_type::CustomAnaglyphMatrixLeft, m_widget->get_stereo_config().get_custom_matrix(true)); + m_emu_settings->SetMapSetting(emu_settings_type::CustomAnaglyphMatrixRight, m_widget->get_stereo_config().get_custom_matrix(false)); + }); + apply_button->setEnabled(current_mode == stereo_render_mode_options::anaglyph_custom); + + QCheckBox* live_preview_cb = new QCheckBox(tr("Live Preview")); + connect(live_preview_cb, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) + { + stereo_config::s_live_preview_enabled = state == Qt::CheckState::Checked; + }); + + QComboBox* combo = new QComboBox(this); + combo->addItem(tr("Disabled"), static_cast(stereo_render_mode_options::disabled)); + combo->addItem(tr("Red-Green"), static_cast(stereo_render_mode_options::anaglyph_red_green)); + combo->addItem(tr("Red-Blue"), static_cast(stereo_render_mode_options::anaglyph_red_blue)); + combo->addItem(tr("Red-Cyan"), static_cast(stereo_render_mode_options::anaglyph_red_cyan)); + combo->addItem(tr("Magenta-Cyan"), static_cast(stereo_render_mode_options::anaglyph_magenta_cyan)); + combo->addItem(tr("Trioscopic"), static_cast(stereo_render_mode_options::anaglyph_trioscopic)); + combo->addItem(tr("Amber-Blue"), static_cast(stereo_render_mode_options::anaglyph_amber_blue)); + combo->addItem(tr("Custom"), static_cast(stereo_render_mode_options::anaglyph_custom)); + combo->setCurrentIndex(combo->findData(static_cast(current_mode))); + + connect(combo, &QComboBox::currentIndexChanged, this, [this, combo, apply_button](int /*index*/) + { + apply_button->setEnabled(static_cast(combo->currentData().toInt()) == stereo_render_mode_options::anaglyph_custom); + m_widget->set_option(static_cast(combo->currentData().toInt())); + update_matrices(); + }); + + m_matrix_left = create_matrix_table(); + m_matrix_right = create_matrix_table(); + + update_matrices(); + + const auto group_box = [](const QString& title, QWidget* widget) + { + QHBoxLayout* layout = new QHBoxLayout(); + layout->addWidget(widget); + + QGroupBox* gb = new QGroupBox(title); + gb->setLayout(layout); + return gb; + }; + + QHBoxLayout* lay_h_1 = new QHBoxLayout(); + lay_h_1->addWidget(combo); + lay_h_1->addWidget(apply_button); + lay_h_1->addWidget(live_preview_cb); + lay_h_1->addSpacerItem(new QSpacerItem(50, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); + + QHBoxLayout* lay_h_2 = new QHBoxLayout(); + lay_h_2->addWidget(group_box(tr("Left matrix"), m_matrix_left)); + lay_h_2->addWidget(group_box(tr("Right matrix"), m_matrix_right)); + + QHBoxLayout* lay_h_3 = new QHBoxLayout(); + lay_h_3->addWidget(group_box(tr("Reference"), m_widget_reference)); + lay_h_3->addWidget(group_box(tr("Anaglyph"), m_widget)); + + QVBoxLayout* lay = new QVBoxLayout(); + lay->addLayout(lay_h_1); + lay->addLayout(lay_h_2); + lay->addLayout(lay_h_3); + + setLayout(lay); + adjustSize(); +} + +QTableWidget* anaglyph_settings_dialog::create_matrix_table() +{ + QTableWidget* table = new QTableWidget(3, 3, this); + table->setVerticalHeaderLabels({tr("R in"), tr("G in"), tr("B in")}); + table->setHorizontalHeaderLabels({tr("R out"), tr("G out"), tr("B out")}); + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + table->setItem(r, c, new QTableWidgetItem("0.0")); + } + } + connect(table, &QTableWidget::cellChanged, this, [this]() + { + apply_custom_matrix(); + }); + + table->resizeColumnsToContents(); + table->resizeRowsToContents(); + return table; +} + +void anaglyph_settings_dialog::read_matrix(mat3f& matrix, QTableWidget* table) const +{ + if (!table) return; + + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + bool ok = false; + const float val = table->item(r, c)->text().toFloat(&ok); + matrix[r].rgb[c] = ok ? val : 0.0f; + } + } +} + +void anaglyph_settings_dialog::update_matrix(QTableWidget* table, const mat3f& matrix, bool is_custom) +{ + if (!table) return; + + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + if (QTableWidgetItem* item = table->item(r, c)) + { + Qt::ItemFlags flags = item->flags(); + flags.setFlag(Qt::ItemIsEditable, is_custom); + + item->setFlags(flags); + item->setText(QString::number(matrix[r].rgb[c])); + } + } + } +} + +void anaglyph_settings_dialog::update_matrices() +{ + if (!m_widget) return; + + const stereo_config& stereo_cfg = m_widget->get_stereo_config(); + const stereo_config::stereo_matrices& matrices = stereo_cfg.matrices(); + + stereo_config::set_live_matrices(matrices); + + const bool is_custom = stereo_cfg.stereo_mode() == stereo_render_mode_options::anaglyph_custom; + + m_updating = true; + update_matrix(m_matrix_left, matrices.left, is_custom); + update_matrix(m_matrix_right, matrices.right, is_custom); + m_updating = false; +} + +void anaglyph_settings_dialog::apply_custom_matrix() +{ + if (!m_widget || m_updating) return; + + stereo_config::stereo_matrices m {}; + read_matrix(m.left, m_matrix_left); + read_matrix(m.right, m_matrix_right); + m_widget->set_custom_matrices(m); + + stereo_config::set_live_matrices(m); +} diff --git a/rpcs3/rpcs3qt/anaglyph_settings_dialog.h b/rpcs3/rpcs3qt/anaglyph_settings_dialog.h new file mode 100644 index 0000000000..7947d6dcaa --- /dev/null +++ b/rpcs3/rpcs3qt/anaglyph_settings_dialog.h @@ -0,0 +1,54 @@ +#pragma once + +#include "emu_settings.h" +#include "Utilities/stereo_config.h" + +#include +#include +#include + +class color_wedge_widget : public QWidget +{ +public: + color_wedge_widget(QWidget* parent = nullptr); + virtual ~color_wedge_widget(); + + void set_option(stereo_render_mode_options option); + void set_custom_matrices(const stereo_config::stereo_matrices& matrices); + + const stereo_config& get_stereo_config() const { return m_stereo_config; } + +protected: + QVector3D apply_matrix(const QVector3D& left, const QVector3D& right, const stereo_config::stereo_matrices& m); + + QVector3D apply_anaglyph_matrix(const QVector3D& left, const QVector3D& right); + + void paintEvent(QPaintEvent* event) override; + + stereo_config m_stereo_config = stereo_config(false); + QImage m_img; +}; + +class anaglyph_settings_dialog : public QDialog +{ +public: + anaglyph_settings_dialog(QWidget* parent, std::shared_ptr emu_settings); + +private: + std::shared_ptr m_emu_settings; + color_wedge_widget* m_widget_reference = nullptr; + color_wedge_widget* m_widget = nullptr; + QTableWidget* m_matrix_left = nullptr; + QTableWidget* m_matrix_right = nullptr; + bool m_updating = false; + + QTableWidget* create_matrix_table(); + + void read_matrix(mat3f& matrix, QTableWidget* table) const; + + void update_matrix(QTableWidget* table, const mat3f& matrix, bool is_custom); + + void update_matrices(); + + void apply_custom_matrix(); +}; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index c9a4cd3a56..6e90adc8c4 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -254,8 +254,10 @@ bool emu_settings::ValidateSettings(bool cleanup) if (cfg_node) { - // Ignore every node in Log subsection - if (level == 0 && cfg_node->get_name() == "Log") + // Ignore every node in map subsections + if (cfg_node->get_type() == cfg::type::log || + cfg_node->get_type() == cfg::type::map || + cfg_node->get_type() == cfg::type::node_map) { continue; } @@ -1503,6 +1505,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case stereo_render_mode_options::anaglyph_magenta_cyan: return tr("Anaglyph Magenta-Cyan", "3D Display Mode"); case stereo_render_mode_options::anaglyph_trioscopic: return tr("Anaglyph Green-Magenta (Trioscopic)", "3D Display Mode"); case stereo_render_mode_options::anaglyph_amber_blue: return tr("Anaglyph Amber-Blue (ColorCode 3D)", "3D Display Mode"); + case stereo_render_mode_options::anaglyph_custom: return tr("Anaglyph Custom", "3D Display Mode"); } break; case emu_settings_type::MidiDevices: diff --git a/rpcs3/rpcs3qt/emu_settings_type.cpp b/rpcs3/rpcs3qt/emu_settings_type.cpp index afcd6949a3..6a9ff966b5 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.cpp +++ b/rpcs3/rpcs3qt/emu_settings_type.cpp @@ -144,6 +144,10 @@ const std::map settings_location = { emu_settings_type::ShaderLoadBgDarkening, get_cfg_location(local_cfg.video.shader_preloading_dialog.darkening_strength) }, { emu_settings_type::ShaderLoadBgBlur, get_cfg_location(local_cfg.video.shader_preloading_dialog.blur_strength) }, + // Anaglyph matrix + { emu_settings_type::CustomAnaglyphMatrixLeft, get_cfg_location(local_cfg.video.custom_anaglyph_matrices.left) }, + { emu_settings_type::CustomAnaglyphMatrixRight, get_cfg_location(local_cfg.video.custom_anaglyph_matrices.right) }, + // Audio { emu_settings_type::AudioRenderer, get_cfg_location(local_cfg.audio.renderer) }, { emu_settings_type::DumpToFile, get_cfg_location(local_cfg.audio.dump_to_file) }, diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 313c92a640..d6105beb1f 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -112,6 +112,10 @@ enum class emu_settings_type RecordWithOverlays, DisableHWTexelRemapping, + // Anaglyph Matrix + CustomAnaglyphMatrixLeft, + CustomAnaglyphMatrixRight, + // Performance Overlay PerfOverlayEnabled, PerfOverlayFramerateGraphEnabled, diff --git a/rpcs3/rpcs3qt/gl_gs_frame.h b/rpcs3/rpcs3qt/gl_gs_frame.h index d1129f8e3a..223ac3cfd1 100644 --- a/rpcs3/rpcs3qt/gl_gs_frame.h +++ b/rpcs3/rpcs3qt/gl_gs_frame.h @@ -6,8 +6,8 @@ struct GLContext { - QSurface *surface = nullptr; - QOpenGLContext *handle = nullptr; + QSurface* surface = nullptr; + QOpenGLContext* handle = nullptr; bool owner = false; }; @@ -15,7 +15,7 @@ class gl_gs_frame : public gs_frame { private: QSurfaceFormat m_format; - GLContext *m_primary_context = nullptr; + GLContext* m_primary_context = nullptr; public: explicit gl_gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, std::shared_ptr gui_settings, bool force_fullscreen); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 19e5565cc2..fabc1f024e 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -24,6 +24,7 @@ #include "render_creator.h" #include "microphone_creator.h" #include "log_level_dialog.h" +#include "anaglyph_settings_dialog.h" #include "Emu/NP/rpcn_countries.h" #include "Emu/GameInfo.h" @@ -45,7 +46,7 @@ LOG_CHANNEL(cfg_log, "CFG"); -std::pair get_data(const QComboBox* box, int index) +static std::pair get_data(const QComboBox* box, int index) { if (!box) return {}; @@ -57,7 +58,7 @@ std::pair get_data(const QComboBox* box, int index) return { var_list[0].toString(), var_list[1].toInt() }; } -int find_item(const QComboBox* box, int value) +static int find_item(const QComboBox* box, int value) { for (int i = 0; box && i < box->count(); i++) { @@ -70,7 +71,7 @@ int find_item(const QComboBox* box, int value) return -1; } -void remove_item(QComboBox* box, int data_value, int def_value) +static void remove_item(QComboBox* box, int data_value, int def_value) { if (!box) return; @@ -593,9 +594,15 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->stereoRenderMode->setEnabled(stereo_allowed && stereo_enabled); ui->stereoRenderEnabled->setEnabled(stereo_allowed); ui->gb_screen_size->setEnabled(stereo_allowed && stereo_enabled); + ui->gb_anaglyph_settings->setEnabled(stereo_allowed && stereo_enabled); }; connect(ui->resBox, &QComboBox::currentIndexChanged, this, [enable_3D_modes](int){ enable_3D_modes(); }); connect(ui->stereoRenderEnabled, &QCheckBox::checkStateChanged, this, [enable_3D_modes](Qt::CheckState){ enable_3D_modes(); }); + connect(ui->pb_anaglyph_settings, &QAbstractButton::clicked, [this]() + { + anaglyph_settings_dialog* dlg = new anaglyph_settings_dialog(this, m_emu_settings); + dlg->open(); + }); enable_3D_modes(); } else @@ -603,6 +610,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->stereoRenderMode->setCurrentIndex(find_item(ui->stereoRenderMode, static_cast(g_cfg.video.stereo_render_mode.def))); ui->stereoRenderEnabled->setChecked(false); ui->gb_stereo->setEnabled(false); + ui->gb_anaglyph_settings->setEnabled(false); } // Checkboxes: main options diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 82232fbeb8..a8f17f0310 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -557,6 +557,22 @@ + + + + Anaglyph Settings + + + + + + Configure + + + + + +