diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt index 7e375107e..264771166 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/IntSetting.kt @@ -49,9 +49,8 @@ enum class IntSetting( if (hMDType == VRUtils.HMDType.QUEST3.value) 1 else 2), VR_CPU_LEVEL("vr_cpu_level", Settings.SECTION_VR, 3), VR_IMMERSIVE_MODE("vr_immersive_mode", Settings.SECTION_VR, 0), - VR_IMMERSIVE_POSITIONAL_FACTOR("vr_immersive_positional_factor", Settings.SECTION_VR, 0), VR_IMMERSIVE_POSITIONAL_GAME_SCALER("vr_immersive_positional_game_scaler", Settings.SECTION_VR, 0), - VR_SI_MODE_REGISTER_OFFSET("vr_si_mode_register_offset", Settings.SECTION_VR, 0); + VR_SI_MODE_REGISTER_OFFSET("vr_si_mode_register_offset", Settings.SECTION_VR, -1); override var int: Int = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt index 87b425f5b..442215302 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/model/StringSetting.kt @@ -15,7 +15,8 @@ enum class StringSetting( CAMERA_OUTER_LEFT_NAME("camera_outer_left_name", Settings.SECTION_CAMERA, "ndk"), CAMERA_OUTER_LEFT_CONFIG("camera_outer_left_config", Settings.SECTION_CAMERA, "_back"), CAMERA_OUTER_RIGHT_NAME("camera_outer_right_name", Settings.SECTION_CAMERA, "ndk"), - CAMERA_OUTER_RIGHT_CONFIG("camera_outer_right_config", Settings.SECTION_CAMERA, "_back"); + CAMERA_OUTER_RIGHT_CONFIG("camera_outer_right_config", Settings.SECTION_CAMERA, "_back"), + VR_IMMMERSIVE_EYE_INDICATOR("vr_immersive_eye_indicator", Settings.SECTION_VR, ""); override var string: String = defaultValue diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt index 0d35000cd..10b0217a7 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1125,18 +1125,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.VR_IMMERSIVE_MODE.defaultValue ) ) - add( - SliderSetting( - IntSetting.VR_IMMERSIVE_POSITIONAL_FACTOR, - R.string.vr_immersive_pos_factor_title, - R.string.vr_immersive_pos_factor_description, - 0, - 40, - "x", - IntSetting.VR_IMMERSIVE_POSITIONAL_FACTOR.key, - IntSetting.VR_IMMERSIVE_POSITIONAL_FACTOR.defaultValue.toFloat() - ) - ) add( SingleChoiceSetting( IntSetting.VR_IMMERSIVE_POSITIONAL_GAME_SCALER, @@ -1153,13 +1141,21 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) IntSetting.VR_SI_MODE_REGISTER_OFFSET, R.string.vr_si_mode_register_offset_title, R.string.vr_si_mode_register_offset_description, - 0, + -1, 92, "register", IntSetting.VR_SI_MODE_REGISTER_OFFSET.key, IntSetting.VR_SI_MODE_REGISTER_OFFSET.defaultValue.toFloat() ) ) + add( + StringInputSetting( + StringSetting.VR_IMMMERSIVE_EYE_INDICATOR, + R.string.vr_immersive_eye_indicator_title, + R.string.vr_immersive_eye_indicator_description, + "" + ) + ) } } } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index 71549cec4..dcdcb5013 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -290,15 +290,22 @@ void Config::ReadValues() { "VR", "vr_immersive_mode", 0); Settings::values.vr_immersive_mode = VRSettings::values.vr_immersive_mode; VRSettings::values.vr_si_mode_register_offset = sdl2_config->GetInteger( - "VR", "vr_si_mode_register_offset", 0); + "VR", "vr_si_mode_register_offset", -1); Settings::values.vr_si_mode_register_offset = VRSettings::values.vr_si_mode_register_offset; - VRSettings::values.vr_immersive_positional_factor = sdl2_config->GetInteger( - "VR", "vr_immersive_positional_factor", 0); - Settings::values.vr_immersive_positional_factor = VRSettings::values.vr_immersive_positional_factor; + + // For immersive modes we use the factor_3d value as a camera movement factor + // which means it affects stereo separation and positional movement + // We have to divide this by 10 or the numbers are too big + VRSettings::values.vr_factor_3d = sdl2_config->GetInteger( + "Renderer", "factor_3d", 100) / 10; VRSettings::values.vr_immersive_positional_game_scaler = sdl2_config->GetInteger( "VR", "vr_immersive_positional_game_scaler", 0); Settings::values.vr_immersive_positional_game_scaler = VRSettings::values.vr_immersive_positional_game_scaler; + VRSettings::values.vr_immersive_eye_indicator = sdl2_config->GetString( + "VR", "vr_immersive_eye_indicator", ""); + Settings::values.vr_immersive_eye_indicator = VRSettings::values.vr_immersive_eye_indicator; + if (Settings::values.vr_immersive_mode.GetValue() > 0) { LOG_INFO(Config, "VR immersive mode enabled"); diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp index 8b4ff3ef1..3ef85af54 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp @@ -442,13 +442,20 @@ XrPosef GameSurfaceLayer::GetTopPanelFromHeadPose(uint32_t eye, const XrPosef& h XrVector3f forward, up, right; XrMath::Quatf::ToVectors(headPose.orientation, forward, right, up); - panelPosition.z += kSuperImmersiveRadius * (forward.x * 0.4f); - panelPosition.y -= kSuperImmersiveRadius * (forward.z * 0.4f); - panelPosition.x += kSuperImmersiveRadius * (forward.y * 0.4f); + panelPosition.z += kSuperImmersiveRadius * (forward.x * 0.58f); + panelPosition.y -= kSuperImmersiveRadius * (forward.z * 0.58f); + panelPosition.x += kSuperImmersiveRadius * (forward.y * 0.58f); - panelPosition.z += up.x / 12.f; - panelPosition.y -= up.z / 12.f; - panelPosition.x += up.y / 12.f; + panelPosition.z += up.x / 25.f; + panelPosition.y -= up.z / 25.f; + panelPosition.x += up.y / 25.f; + + if (mImmersiveMode == 3) + { + panelPosition.z += right.x * (0.065f / 2.f) * (1 - 2.f * eye); + panelPosition.y -= right.z * (0.065f / 2.f) * (1 - 2.f * eye); + panelPosition.x += right.y * (0.065f / 2.f) * (1 - 2.f * eye); + } return XrPosef{headPose.orientation, panelPosition}; } diff --git a/src/android/app/src/main/jni/vr/vr_main.cpp b/src/android/app/src/main/jni/vr/vr_main.cpp index c80bedd93..221f33c68 100644 --- a/src/android/app/src/main/jni/vr/vr_main.cpp +++ b/src/android/app/src/main/jni/vr/vr_main.cpp @@ -75,7 +75,7 @@ std::chrono::time_point gOnCreateStartTime; std::unique_ptr gOpenXr; MessageQueue gMessageQueue; -const std::vector immersiveScaleFactor = {1.0f, 3.0f, 1.8f}; +const std::vector immersiveScaleFactor = {1.0f, 3.0f, 1.4f}; void ForwardButtonStateChangeToCitra(JNIEnv* jni, jobject activityObject, jmethodID forwardVRInputMethodID, const int androidButtonCode, @@ -433,12 +433,12 @@ private: -MATH_FLOAT_PI / 8.0f) || // If in "super immersive" mode then put controller next to head in order to // disable the mode temporarily - (VRSettings::values.vr_immersive_mode > 2 && length < 0.2)) { + (VRSettings::values.vr_immersive_mode >= 2 && length < 0.2)) { XrVector4f identity[4] = {}; XrMath::Matrixf::Identity(identity); immersiveModeFactor = 1.0f; Core::System::GetInstance().GPU().Renderer().Rasterizer()->SetVRData( - 1, immersiveModeFactor, -1, (float*)identity); + 1, immersiveModeFactor, -1, 0.f, (float*)identity); } else { XrVector4f transform[4] = {}; XrMath::Quatf::ToRotationMatrix(gOpenXr->headLocation.pose.orientation, @@ -454,20 +454,16 @@ private: invertedOrientation, gOpenXr->headLocation.pose.position); const float gamePosScaler = - powf(10.f, VRSettings::values.vr_immersive_positional_game_scaler); - inv_transform[3].x = -position.x * - VRSettings::values.vr_immersive_positional_factor * - gamePosScaler; - inv_transform[3].y = -position.y * - VRSettings::values.vr_immersive_positional_factor * - gamePosScaler; - inv_transform[3].z = -position.z * - VRSettings::values.vr_immersive_positional_factor * - gamePosScaler; + powf(10.f, VRSettings::values.vr_immersive_positional_game_scaler) * + VRSettings::values.vr_factor_3d; + + inv_transform[3].x = -position.x * gamePosScaler; + inv_transform[3].y = -position.y * gamePosScaler; + inv_transform[3].z = -position.z * gamePosScaler; Core::System::GetInstance().GPU().Renderer().Rasterizer()->SetVRData( VRSettings::values.vr_immersive_mode, immersiveModeFactor, uoffset, - (float*)inv_transform); + -gamePosScaler, (float*)inv_transform); showLowerPanel = false; } } diff --git a/src/android/app/src/main/jni/vr/vr_settings.h b/src/android/app/src/main/jni/vr/vr_settings.h index 56c0f7217..25648552c 100644 --- a/src/android/app/src/main/jni/vr/vr_settings.h +++ b/src/android/app/src/main/jni/vr/vr_settings.h @@ -45,8 +45,9 @@ struct Values { int32_t vr_immersive_mode = 0; bool extra_performance_mode_enabled = false; int32_t vr_si_mode_register_offset = -1; - int32_t vr_immersive_positional_factor = 0; + int32_t vr_factor_3d = 100; int32_t vr_immersive_positional_game_scaler = 0; + std::string vr_immersive_eye_indicator; } extern values; } // namespace VRSettings diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 93671ca91..e4387c407 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -453,9 +453,9 @@ Off - 180 Degree Immersive (3DoF) - Super Immersive (6DoF) - Profile 1 - Super Immersive (6DoF) - Profile 2 + 180 Degree Wrap-Around Immersive (3DoF) + "Super Immersive (6DoF) - Profile 1 (Ocarina of Time & Majora's Mask)" + Super Immersive (6DoF) - Profile 2 (All other games) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 60de10dab..3460c8db6 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -683,10 +683,10 @@ Some games just won\'t run well. Be sure to check the CitraVR Game Compatibility List to see which games perform best. \nHaving trouble? Press Select + Start to disable Gamepad Gaze Mode, then try again Immersive Mode - Register Offset - Sets the register offset used for the view when using Super Immersive Mode Profile 2 - varies by game - Positional Movement Factor - Adjusts how much movement left/right/up/down affects the game camera (0 = disabled) - Positional Movement Factor Multiplier - Adjusts how much movement left/right/up/down affects the game camera, some games required larger values, so this multiplies the Positional Movement Factor + Register Offset [Optional] + [default: -1 = use auto-detect] CAUTION: Sets the register offset used for the view when using Super Immersive Mode Profile 2 + Eye Indicator Register [Optional] + [Leave blank for auto-detect] CAUTION: Sets the register and index offset used for identifying which eye is being drawn e.g 87,2 + Depth Factor Multiplier + Adjusts how much the depth slider affects the game camera (movement/separation), some games require smaller/larger values, so this multiplier adjusts how much movement the depth setting causes diff --git a/src/common/settings.h b/src/common/settings.h index bbcdb35e5..41c45e3e7 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -557,9 +557,9 @@ struct Values { // VR Setting vr_immersive_mode{0, "vr_immersive_mode"}; - Setting vr_si_mode_register_offset{-1, "vr_si_mode_register_offset"}; - Setting vr_immersive_positional_factor{0, "vr_immersive_positional_factor"}; + Setting vr_si_mode_register_offset{-1, "vr_si_mode_register_offset"}; Setting vr_immersive_positional_game_scaler{0, "vr_immersive_positional_game_scaler"}; + Setting vr_immersive_eye_indicator{"", "vr_immersive_eye_indicator"}; }; extern Values values; diff --git a/src/core/hle/kernel/shared_page.cpp b/src/core/hle/kernel/shared_page.cpp index ef7f1690c..452ea7fe1 100644 --- a/src/core/hle/kernel/shared_page.cpp +++ b/src/core/hle/kernel/shared_page.cpp @@ -85,7 +85,9 @@ Handler::Handler(Core::Timing& timing, u64 override_init_time) : timing(timing) std::bind(&Handler::UpdateTimeCallback, this, _1, _2)); timing.ScheduleEvent(0, update_time_event, 0, 0); - float slidestate = Settings::values.factor_3d.GetValue() / 100.0f; + float slidestate = Settings::values.vr_immersive_mode.GetValue() < 3 ? + Settings::values.factor_3d.GetValue() / 100.0f : + 0.01f; shared_page.sliderstate_3d = static_cast(slidestate); // TODO(PabloMK7) diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 9013fd4b1..bf0a06422 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -217,9 +217,10 @@ void Module::UpdatePadCallback(std::uintptr_t user_data, s64 cycles_late) { // TODO(xperia64): How the 3D Slider is updated by the HID module needs to be RE'd // and possibly moved to its own Core::Timing event. - mem->pad.sliderstate_3d = (Settings::values.factor_3d.GetValue() / 100.0f); - system.Kernel().GetSharedPageHandler().Set3DSlider(Settings::values.factor_3d.GetValue() / - 100.0f); + mem->pad.sliderstate_3d = Settings::values.vr_immersive_mode.GetValue() < 3 ? + (Settings::values.factor_3d.GetValue() / 100.0f) : + 0.01f; + system.Kernel().GetSharedPageHandler().Set3DSlider(mem->pad.sliderstate_3d); // Reschedule recurrent event system.CoreTiming().ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event); diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index 45fa9e16e..ba1361545 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp @@ -22,6 +22,8 @@ static Common::Vec3f LightColor(const Pica::LightingRegs::LightColor& color) { return Common::Vec3u{color.r, color.g, color.b} / 255.0f; } +constexpr float VR_IPD = 0.065f; + RasterizerAccelerated::HardwareVertex::HardwareVertex(const Pica::OutputVertex& v, bool flip_quaternion) { position[0] = v.pos.x.ToFloat32(); @@ -859,7 +861,7 @@ void RasterizerAccelerated::SyncClipPlane() { } } -void RasterizerAccelerated::SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float view[16]) +void RasterizerAccelerated::SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float& gamePosScaler, const float inv_view[16]) { if (vs_uniform_block_data.data.vr_immersive_mode_factor != immersiveModeFactor) { @@ -869,7 +871,8 @@ void RasterizerAccelerated::SetVRData(const int32_t &vrImmersiveMode, const floa vr_uoffset = uoffset; vr_immersive_mode = vrImmersiveMode; - std::memcpy(vr_view, view, sizeof(float) * 16); + vr_game_pos_scaler = gamePosScaler; + std::memcpy(vr_inv_view, inv_view, sizeof(float) * 16); } static void MatrixTranspose(float m[16], const float src[16]) { @@ -924,37 +927,275 @@ void RasterizerAccelerated::ApplyVRDataToPicaVSUniforms(Pica::Shader::Generator: viewMatrixIndex = vr_uoffset; break; default: - viewMatrixIndex = -1; - break; + return; + } + + const bool findLeftRightEyeIndicator = Settings::values.vr_immersive_eye_indicator.GetValue().empty(); + + /* + * The following section is a "heuristic" algorithm that guesses (pretty well actually!) both the number of the + * register for the view transformation matrix and also the left/right eye indicator register based on a bunch of + * characteristics I gathered by looking at the register values for a few games. If it gets it wrong, then it is + * still possible to supply your own values using the config options, but the default now should be using this + * "auto-detect" routine. + */ + if (viewMatrixIndex == -1 && mode >= 3) + { + struct regscore + { + regscore() { + neg = pos = 0; + score = bonus = 0.f; + } + int neg; + int pos; + std::set values; + float score; + float bonus; + }; + + //static array to keep track of the way the data changes in each register location + static regscore regscores[96 * 4]; + static regscore regscores2[96]; + + // use a counter to ignore the first number of + // times through here, as a lot of these will + // just be things like intro videos etc + static int counter = 0; + constexpr int start = 5000; + constexpr int stop = 250000; + constexpr float EPSILON = 0.00001f; + auto between = [=](float l, float v, float r) { return v <= r && v >= l; }; + static int identityMatrix[16] = { + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + + if (++counter > start) + { + //Stop collecting after a period of time as we should have a good hit by now + //we don't want to waste precious CPU + if (counter < stop) + { + for (int r = 0; r < 96; ++r) + { + /* + * This block does the scoring for the possible view matrix register + */ + if (r <= 91) + { + //Check that all the values are between -1 and 1 + //don't include values that might be positional and values in the final 4x4 row + bool valid = true; + for (int i = 0; i < 3 && valid; ++i) { + for (int j = 0; j < 3 && valid; ++j) { + valid = between(-1.f, f[r + i][j], 1.f); + } + } + + //only proceed to score if register could legitimately be a view matrix + if (valid) { + regscores2[r].score += 1; + + //bonus point if register appear to be an identity matrix + bool isIdentity = true; + for (int i = 0; i < 3 && isIdentity; ++i) { + for (int j = 0; j < 3 && isIdentity; ++j) { + isIdentity = (int)std::roundf(f[r + i][j]) == identityMatrix[i * 4 + j]; + } + } + if (isIdentity) { + regscores2[r].score += 1; + } + + //another bonus point if the absolute position values in the matrix are bigger than 1 + if (fabsf(f[r].w) > 1.f && fabsf(f[r+1].w) > 1.f && fabsf(f[r+2].w) > 1.f) { + regscores2[r].score += 1; + } + + //another bonus point if the last position contains 0, 1, 2, 3 or 0, 0, 0, 1 + if ((f[r + 3].x == 0.f && f[r + 3].y == 0.f && f[r + 3].z == 0.f && + f[r + 3].w == 1.f) || + (f[r + 3].x == 0.f && f[r + 3].y == 1.f && f[r + 3].z == 2.f && + f[r + 3].w == 3.f)) + { + regscores2[r].score += 1; + } + + //Certain values in the register should never be exactly 0 + if (f[r].x == 0.f || f[r + 1].y == 0.f || f[r + 2].z == 0.f) { + regscores2[r].score -= 2; + } + + // final bonus point if register is a commonly occuring one + if (r == 90 || r == 4 || r == 8) { + regscores2[r].score += 1; + } + } else { + //Not possibly a view matrix, subtract a point as punishment, but don't go below 0 + if (regscores2[r].score > 0) + regscores2[r].score -= 1; + } + } + + /* + * This block does the scoring for the possible left/right eye indicator register, typical characteristics + * of that register are: + * * Its value has a more or less equal number of negative and positive values over time + * * It is a small number greater than almost 0 and less than 0.1 + * * It only has a small amount of variance in its values (rounded to 5dp) + * * Frequently selected registers get a small bonus, as they then become more likely to be the correct ones + */ + for (int i = 0; i <= 3; ++i) + { + float value = fabsf(f[r][i]); + + auto ®score = regscores[r * 4 + i]; + + if (value < 0.1f && value > EPSILON) + { + if (f[r][i] < 0.f) + { + regscore.neg++; + } + else + { + regscore.pos++; + } + + //Store the value rounded, we are looking for the register with the least variance + regscore.values.insert( + floorf(value * 10000) / 10000); + } + + // recalc score + if (regscore.neg > 0 && + regscore.pos > 0 && + regscore.values.size() > 0) + { + int max = std::max(regscore.neg, + regscore.pos); + int min = std::min(regscore.neg, + regscore.pos); + regscore.score = + ((min * 1000.0f) / + (float) (max * + regscore.values.size())) * + ((float) (min + max) / (counter - start)); + + //If this register has only seen 1 or two legit values, then punish it + if (regscore.values.size() < 2) + { + regscore.score /= 10.0f; + } + + //Add on any bonuses this register has received + regscore.score += regscore.bonus; + } + } + } + + //Now find the highest scoring registers/index + float topscore = 0.f; + float mat_topscore = 0.f; + for (int r = 0; r < 96; ++r) + { + if (regscores2[r].score > mat_topscore) { + vr_heuristic.view_matrixregister = r; + mat_topscore = regscores2[r].score; + } + + for (int i = 0; i <= 3; ++i) + { + if (regscores[r * 4 + i].score > topscore) + { + vr_heuristic.eye_indicator_register = r; + vr_heuristic.eye_indicator_reg_index = i; + topscore = regscores[r * 4 + i].score; + } + } + } + + if (mat_topscore != 0.f) { + regscores2[vr_heuristic.view_matrixregister].score += 0.05f; // small perk for being selected + } + + if (topscore != 0.0f) + { + regscores[vr_heuristic.eye_indicator_register * 4 + + vr_heuristic.eye_indicator_reg_index].bonus += 0.001f; // small perk for being selected + } + } + + viewMatrixIndex = vr_heuristic.view_matrixregister; + } } if (viewMatrixIndex != -1 && vs_uniforms.uniforms.f.size() > viewMatrixIndex) { if (matrixMode == 2) { - f[viewMatrixIndex][0] = vr_view[0]; - f[viewMatrixIndex][1] = vr_view[4]; - f[viewMatrixIndex][2] = vr_view[8]; - f[viewMatrixIndex + 1][0] = vr_view[1]; - f[viewMatrixIndex + 1][1] = vr_view[5]; - f[viewMatrixIndex + 1][2] = vr_view[9]; - f[viewMatrixIndex + 2][0] = vr_view[2]; - f[viewMatrixIndex + 2][1] = vr_view[6]; - f[viewMatrixIndex + 2][2] = vr_view[10]; + f[viewMatrixIndex][0] = vr_inv_view[0]; + f[viewMatrixIndex][1] = vr_inv_view[4]; + f[viewMatrixIndex][2] = vr_inv_view[8]; + f[viewMatrixIndex + 1][0] = vr_inv_view[1]; + f[viewMatrixIndex + 1][1] = vr_inv_view[5]; + f[viewMatrixIndex + 1][2] = vr_inv_view[9]; + f[viewMatrixIndex + 2][0] = vr_inv_view[2]; + f[viewMatrixIndex + 2][1] = vr_inv_view[6]; + f[viewMatrixIndex + 2][2] = vr_inv_view[10]; } - else if (matrixMode >= 3) + else if (matrixMode == 3) { + // First apply the view transformation matrix to the right location float v[16], v2[16], v3[16]; MatrixTranspose(v, &f[viewMatrixIndex].x); - std::memcpy(v2, vr_view, sizeof(float) * 16); + std::memcpy(v2, vr_inv_view, sizeof(float) * 16); v2[12] = v2[13] = v2[14] = 0.f; MatrixMultiply(v3, v2, v); MatrixTranspose(&f[viewMatrixIndex].x, v3); + + // This following part is an improvement on just using the default stereo separation provided by the 3DS + // The problem with the default is it doesn't work correctly when rotating the headset, whereas the following approach + // applies a left/right separation from an (almost) centered camera POV. + // This profile mode will set a depth scale of only 0.1%, and then use a defined vr uniform register + // to identify if the left or right eye is being drawn and apply appropriate stereo separation + // + // The pair of values (e.g "87,2") represents the offset of the Vec4f in the vs pica uniforms and the + // index into that Vec4 of the value that is check for: -ve for left eye and +ve for right eye + float eye_indicator_register = vr_heuristic.eye_indicator_register; + float eye_indicator_reg_index = vr_heuristic.eye_indicator_reg_index; + //If the user _has_ defined this (unlikely!) then use the config setting + if (!findLeftRightEyeIndicator) + { + // Finding these offset/index values manually is easier to do using a desktop build of Citra in a debugger + // however the heuristic search appears to do a very good job of this so it is unlikely people + // will need to find and specify this config, but is still available to set if it fails to work for + // a game and someone identifies the appropriate values + const std::string vr_immersive_eye_indicator = Settings::values.vr_immersive_eye_indicator.GetValue(); + eye_indicator_register = atoi(vr_immersive_eye_indicator.substr(0, vr_immersive_eye_indicator.find_first_of(',')).c_str()); + eye_indicator_reg_index = atoi(vr_immersive_eye_indicator.substr(vr_immersive_eye_indicator.find_first_of(',')+1).c_str()); + } + + //If we found/know a viable register/index, then use it for left/right eye logic + if (eye_indicator_register != -1 && + eye_indicator_reg_index != -1 && + eye_indicator_register < vs_uniforms.uniforms.f.size() && + (eye_indicator_register < viewMatrixIndex || + eye_indicator_register > viewMatrixIndex + 3) && + f[eye_indicator_register][eye_indicator_reg_index] != 0.0f) + { + const bool isLeftEye = (f[eye_indicator_register][eye_indicator_reg_index] < 0.f); + f[viewMatrixIndex][3] += + vr_game_pos_scaler * (isLeftEye ? -1 : 1) * (VR_IPD / 2.f); + } } - f[viewMatrixIndex][3] += vr_view[12]; - f[viewMatrixIndex + 1][3] += vr_view[13]; - f[viewMatrixIndex + 2][3] += vr_view[14]; + f[viewMatrixIndex][3] += vr_inv_view[12]; + f[viewMatrixIndex + 1][3] += vr_inv_view[13]; + f[viewMatrixIndex + 2][3] += vr_inv_view[14]; } } } diff --git a/src/video_core/rasterizer_accelerated.h b/src/video_core/rasterizer_accelerated.h index 8cc72a9a6..61739c9d9 100644 --- a/src/video_core/rasterizer_accelerated.h +++ b/src/video_core/rasterizer_accelerated.h @@ -4,7 +4,6 @@ #pragma once -#include "common/vector_math.h" #include "video_core/rasterizer_interface.h" #include "video_core/shader/generator/pica_fs_config.h" #include "video_core/shader/generator/shader_uniforms.h" @@ -31,7 +30,7 @@ public: void SyncEntireState() override; - void SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float view[16]) override; + void SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float& gamePosScaler, const float inv_view[16]) override; protected: /// Sync fixed-function pipeline state @@ -173,12 +172,19 @@ protected: std::array proctex_diff_lut_data{}; //VR Stuff - u32 vr_uoffset = -1; - u32 vr_immersive_mode; - float vr_view[16] = {}; + u32 vr_uoffset = 0; + u32 vr_immersive_mode; + float vr_game_pos_scaler = 0.f; + float vr_inv_view[16] = {}; + + struct HeuristicResult + { + int32_t view_matrixregister = -1; + int32_t eye_indicator_register = -1; + int32_t eye_indicator_reg_index = -1; + } vr_heuristic; public: - void ApplyVRDataToPicaVSUniforms(Pica::Shader::Generator::VSPicaUniformData &vs_uniforms); }; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 8d6a917d1..b73bd10bd 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -84,6 +84,6 @@ public: virtual void SyncEntireState() {} /// Set VR position data on the rasterizer - virtual void SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float view[16]) {} + virtual void SetVRData(const int32_t &vrImmersiveMode, const float& immersiveModeFactor, int uoffset, const float& gamePosScaler, const float inv_view[16]) {} }; } // namespace VideoCore