diff --git a/src/mods/UObjectHook.cpp b/src/mods/UObjectHook.cpp index 8386852da..3819cce07 100644 --- a/src/mods/UObjectHook.cpp +++ b/src/mods/UObjectHook.cpp @@ -612,7 +612,8 @@ void UObjectHook::on_pre_calculate_stereo_view_offset(void* stereo_device, const } // else todo? } - if ((view_index + 1) % 2 == 0) { + // Mono only has a single view pass, so there is no second view to wait for + if ((view_index + 1) % 2 == 0 || VR::get()->is_using_mono()) { tick_attachments(view_rotation, world_to_meters, view_location, is_double); } } diff --git a/src/mods/VR.cpp b/src/mods/VR.cpp index 0b5c53def..f63bbb308 100644 --- a/src/mods/VR.cpp +++ b/src/mods/VR.cpp @@ -1561,7 +1561,7 @@ void VR::update_hmd_state(bool from_view_extensions, uint32_t frame_count) { } } - if (frame_count != 0 && is_using_afr() && frame_count % 2 == 0) { + if (frame_count != 0 && is_using_alternating_eyes() && frame_count % 2 == 0) { if (runtime->is_openxr()) { std::scoped_lock __{ m_openxr->sync_assignment_mtx }; @@ -2060,7 +2060,7 @@ void VR::on_present() { m_present_thread_id = GetCurrentThreadId(); utility::ScopeGuard _guard {[&]() { - if (!is_using_afr() || (m_render_frame_count + 1) % 2 == m_left_eye_interval) { + if (!is_using_alternating_eyes() || (m_render_frame_count + 1) % 2 == m_left_eye_interval) { SetEvent(m_present_finished_event); } @@ -2069,7 +2069,7 @@ void VR::on_present() { m_frame_count = get_runtime()->internal_render_frame_count; - if (!is_using_afr() || m_render_frame_count % 2 == m_left_eye_interval) { + if (!is_using_alternating_eyes() || m_render_frame_count % 2 == m_left_eye_interval) { ResetEvent(m_present_finished_event); } @@ -2126,7 +2126,7 @@ void VR::on_present() { const auto renderer = g_framework->get_renderer_type(); vr::EVRCompositorError e = vr::EVRCompositorError::VRCompositorError_None; - const auto is_left_eye_frame = is_using_afr() ? (m_render_frame_count % 2 == m_left_eye_interval) : true; + const auto is_left_eye_frame = is_using_alternating_eyes() ? (m_render_frame_count % 2 == m_left_eye_interval) : true; if (is_left_eye_frame && get_synchronize_stage() == VR::SynchronizeStage::LATE) { const auto had_sync = runtime->got_first_sync; @@ -2217,7 +2217,7 @@ void VR::on_post_present() { detect_controllers(); - const auto is_left_eye_frame = is_using_afr() ? (is_same_frame || (m_render_frame_count % 2 == m_left_eye_interval)) : true; + const auto is_left_eye_frame = is_using_alternating_eyes() ? (is_same_frame || (m_render_frame_count % 2 == m_left_eye_interval)) : true; if (is_left_eye_frame) { if (get_synchronize_stage() == VR::SynchronizeStage::VERY_LATE || !runtime->got_first_sync) { @@ -2411,7 +2411,11 @@ void VR::on_draw_sidebar_entry(std::string_view name) { } if (selected_page == PAGE_UNREAL) { - m_rendering_method->draw("Rendering Method"); + if (m_rendering_method->draw("Rendering Method")) { + // Mono forces its own projection overrides, so the eye projections need to be regenerated when switching. + get_runtime()->should_recalculate_eye_projections = true; + } + m_synced_afr_method->draw("Synced Sequential Method"); m_world_scale->draw("World Scale"); diff --git a/src/mods/VR.hpp b/src/mods/VR.hpp index 941840c7f..58bb65b52 100644 --- a/src/mods/VR.hpp +++ b/src/mods/VR.hpp @@ -29,6 +29,7 @@ class VR : public Mod { NATIVE_STEREO = 0, SYNCHRONIZED = 1, ALTERNATING = 2, + MONO = 3, }; enum SynchronizeStage { @@ -408,9 +409,22 @@ class VR : public Mod { bool is_using_afr() const { return m_rendering_method->value() == RenderingMethod::ALTERNATING || m_rendering_method->value() == RenderingMethod::SYNCHRONIZED || + m_rendering_method->value() == RenderingMethod::MONO || m_extreme_compat_mode->value() == true; } + // Mono renders a single centered view every frame and submits it to both eyes. + // It shares the single view/single wide render target layout with AFR (is_using_afr() is true) + // but does not alternate eyes between frames. + bool is_using_mono() const { + return m_rendering_method->value() == RenderingMethod::MONO; + } + + // Whether the left and right eyes are rendered on separate (alternating) frames. + bool is_using_alternating_eyes() const { + return is_using_afr() && !is_using_mono(); + } + bool is_using_synchronized_afr() const { return m_rendering_method->value() == RenderingMethod::SYNCHRONIZED || (m_extreme_compat_mode->value() && m_rendering_method->value() == RenderingMethod::NATIVE_STEREO); @@ -624,11 +638,20 @@ class VR : public Mod { return m_extreme_compat_mode->value(); } - auto get_horizontal_projection_override() const { + // Mono needs both eyes to share the exact same frustum, the per-eye view bounds then crop the shared image. + int32_t get_horizontal_projection_override() const { + if (is_using_mono()) { + return HORIZONTAL_PROJECTION_OVERRIDE::HORIZONTAL_SYMMETRIC; + } + return m_horizontal_projection_override->value(); } - auto get_vertical_projection_override() const { + int32_t get_vertical_projection_override() const { + if (is_using_mono() && m_vertical_projection_override->value() == VERTICAL_PROJECTION_OVERRIDE::VERTICAL_DEFAULT) { + return VERTICAL_PROJECTION_OVERRIDE::VERTICAL_MATCHED; + } + return m_vertical_projection_override->value(); } @@ -837,6 +860,7 @@ class VR : public Mod { "Native Stereo", "Synchronized Sequential", "Alternating/AFR", + "Mono", }; static const inline std::vector s_sync_mode_names{ diff --git a/src/mods/vr/D3D11Component.cpp b/src/mods/vr/D3D11Component.cpp index 24b0a05aa..8fb9ebbd2 100644 --- a/src/mods/vr/D3D11Component.cpp +++ b/src/mods/vr/D3D11Component.cpp @@ -259,8 +259,10 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) { const auto is_same_frame = m_last_rendered_frame > 0 && m_last_rendered_frame == vr->m_render_frame_count; m_last_rendered_frame = vr->m_render_frame_count; + // Mono uses the AFR texture layout, but every frame is submitted to both eyes (like the is_same_frame case) + const auto is_mono = vr->is_using_mono(); const auto is_actually_afr = vr->is_using_afr(); - const auto is_afr = !is_same_frame && vr->is_using_afr(); + const auto is_afr = !is_same_frame && vr->is_using_alternating_eyes(); const auto is_left_eye_frame = is_afr && vr->m_render_frame_count % 2 == vr->m_left_eye_interval; const auto is_right_eye_frame = !is_afr || vr->m_render_frame_count % 2 == vr->m_right_eye_interval; @@ -423,7 +425,14 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) { if (!is_afr) { // Render right side to right screen tex - if (m_scene_capture_tex_ref.has_texture() && m_scene_capture_tex_ref.has_srv()) { + if (is_mono) { + render_srv_to_rtv( + m_game_batch.get(), + m_engine_tex_ref, + m_2d_screen_tex[1], + RECT{0, 0, (LONG)((float)m_backbuffer_size[0] / 2.0f), (LONG)m_backbuffer_size[1]} + ); + } else if (m_scene_capture_tex_ref.has_texture() && m_scene_capture_tex_ref.has_srv()) { render_srv_to_rtv( m_game_batch.get(), m_scene_capture_tex_ref, @@ -630,7 +639,7 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) { if (is_actually_afr) { D3D11_BOX src_box{}; if (!vr->is_extreme_compatibility_mode_enabled()) { - if (!is_afr) { + if (!is_afr && !is_mono) { src_box.left = m_backbuffer_size[0] / 2; src_box.right = m_backbuffer_size[0]; src_box.top = 0; @@ -770,7 +779,7 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) { if (!m_scene_capture_tex_ref.has_texture()) { D3D11_BOX src_box{}; if (!vr->is_extreme_compatibility_mode_enabled()) { - if (!is_afr) { + if (!is_afr && !is_mono) { src_box.left = m_backbuffer_size[0] / 2; src_box.right = m_backbuffer_size[0]; src_box.top = 0; diff --git a/src/mods/vr/D3D12Component.cpp b/src/mods/vr/D3D12Component.cpp index 11fdce508..006af1754 100644 --- a/src/mods/vr/D3D12Component.cpp +++ b/src/mods/vr/D3D12Component.cpp @@ -79,8 +79,10 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { const auto is_same_frame = m_last_rendered_frame > 0 && m_last_rendered_frame == vr->m_render_frame_count; m_last_rendered_frame = vr->m_render_frame_count; + // Mono uses the AFR texture layout, but every frame is submitted to both eyes (like the is_same_frame case) + const auto is_mono = vr->is_using_mono(); const auto is_actually_afr = vr->is_using_afr(); - const auto is_afr = !is_same_frame && vr->is_using_afr(); + const auto is_afr = !is_same_frame && vr->is_using_alternating_eyes(); const auto is_left_eye_frame = is_afr && vr->m_render_frame_count % 2 == vr->m_left_eye_interval; const auto is_right_eye_frame = !is_afr || vr->m_render_frame_count % 2 == vr->m_right_eye_interval; @@ -307,7 +309,17 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { if (!is_afr) { // Render right side to right screen tex - if (m_scene_capture_tex.texture.Get() != nullptr) { + if (is_mono) { + d3d12::render_srv_to_rtv( + m_game_batch.get(), + commands.cmd_list.Get(), + m_game_tex, + m_2d_screen_tex[1], + RECT{0, 0, (LONG)((float)m_backbuffer_size[0] / 2.0f), (LONG)m_backbuffer_size[1]}, + ENGINE_SRC_COLOR, + ENGINE_SRC_COLOR + ); + } else if (m_scene_capture_tex.texture.Get() != nullptr) { d3d12::render_srv_to_rtv( m_game_batch.get(), commands.cmd_list.Get(), @@ -519,7 +531,7 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { D3D12_BOX src_box{}; if (!vr->is_extreme_compatibility_mode_enabled()) { - if (!is_afr) { + if (!is_afr && !is_mono) { src_box.left = m_backbuffer_size[0] / 2; src_box.right = m_backbuffer_size[0]; src_box.top = 0; @@ -591,7 +603,7 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { } } - if (!is_afr) { + if (!is_afr && !is_mono) { if (m_scene_capture_tex.texture.Get() == nullptr) { m_openvr.copy_right(backbuffer.Get(), D3D12_RESOURCE_STATE_RENDER_TARGET); } else { diff --git a/src/mods/vr/FFakeStereoRenderingHook.cpp b/src/mods/vr/FFakeStereoRenderingHook.cpp index 148f14391..f92a54097 100644 --- a/src/mods/vr/FFakeStereoRenderingHook.cpp +++ b/src/mods/vr/FFakeStereoRenderingHook.cpp @@ -3066,7 +3066,7 @@ sdk::FSceneView* FFakeStereoRenderingHook::sceneview_constructor(sdk::FSceneView last_frame_count = g_frame_count; - const auto true_index = vr->is_using_afr() ? (g_frame_count + last_index) % 2 : last_index; + const auto true_index = vr->is_using_alternating_eyes() ? (g_frame_count + last_index) % 2 : (vr->is_using_mono() ? 0 : last_index); if (vr->is_splitscreen_compatibility_enabled() || vr->is_sceneview_compatibility_enabled()) { int32_t w = vr->get_hmd_width(); @@ -3177,7 +3177,7 @@ sdk::FSceneView* FFakeStereoRenderingHook::sceneview_constructor(sdk::FSceneView } } - if (init_options_scene_state != nullptr && !new_scene_state_inserted_this_frame && vr->is_ghosting_fix_enabled() && !known_scene_states.empty() && vr->is_using_afr() && true_index == 1) { + if (init_options_scene_state != nullptr && !new_scene_state_inserted_this_frame && vr->is_ghosting_fix_enabled() && !known_scene_states.empty() && vr->is_using_alternating_eyes() && true_index == 1) { init_options->set_stereo_pass(EStereoscopicPass::eSSP_PRIMARY); // Set the scene state to the one that isn't the current one @@ -3507,7 +3507,7 @@ void FFakeStereoRenderingHook::begin_render_viewfamily(ISceneViewExtension* exte int32_t x = 0; int32_t y = 0; - const auto true_index = vr->is_using_afr() ? (frame_count + 1) % 2 : view_index; + const auto true_index = vr->is_using_alternating_eyes() ? (frame_count + 1) % 2 : (vr->is_using_mono() ? 0 : view_index); if (!vr->is_using_afr() && true_index == 1) { x += w; @@ -4790,10 +4790,12 @@ __forceinline void FFakeStereoRenderingHook::calculate_stereo_view_offset( const auto has_double_precision = g_hook->m_has_double_precision; const auto rot_d = (Rotator*)view_rotation; + const auto is_mono = vr->is_using_mono(); + if (vr->is_using_afr() && !is_full_pass) { - true_index = g_frame_count % 2; + true_index = is_mono ? 0 : g_frame_count % 2; - if (!vr->is_using_synchronized_afr()) { + if (!vr->is_using_synchronized_afr() && !is_mono) { if (g_hook->m_has_double_precision) { if (true_index == 1) { *rot_d = g_hook->m_last_afr_rotation_double; @@ -4810,6 +4812,9 @@ __forceinline void FFakeStereoRenderingHook::calculate_stereo_view_offset( } } + // Mono only has a single pass per frame, so it has to act as both the first (left) and the last (right) eye pass. + const auto is_last_eye_pass = true_index == 1 || (is_mono && !is_full_pass); + if (true_index == 0 && !is_full_pass) { if (has_double_precision) { g_hook->m_last_pre_rotation_double = *rot_d; @@ -4927,7 +4932,10 @@ __forceinline void FFakeStereoRenderingHook::calculate_stereo_view_offset( const auto current_eye_rotation_offset = glm::normalize(glm::quat{vr->get_eye_transform(true_index)}); const auto new_rotation = glm::normalize(vqi_norm * current_hmd_rotation * current_eye_rotation_offset); - const auto eye_offset = glm::vec3{vr->get_eye_offset((VRRuntime::Eye)(true_index))}; + // Mono renders from the point directly between the eyes. + const auto eye_offset = is_mono ? + (glm::vec3{vr->get_eye_offset(VRRuntime::Eye::LEFT)} + glm::vec3{vr->get_eye_offset(VRRuntime::Eye::RIGHT)}) * 0.5f : + glm::vec3{vr->get_eye_offset((VRRuntime::Eye)(true_index))}; const auto standing_delta = vr->get_position(0) - vr->get_standing_origin(); @@ -4971,7 +4979,7 @@ __forceinline void FFakeStereoRenderingHook::calculate_stereo_view_offset( // Roomscale movement // only do it on the right eye pass // if we did it on the left, there would be eye desyncs when the right eye is rendered - if (true_index == 1 && (vr->is_roomscale_enabled() || vr->is_aim_pawn_control_rotation_enabled())) { + if (is_last_eye_pass && (vr->is_roomscale_enabled() || vr->is_aim_pawn_control_rotation_enabled())) { const auto world = sdk::UEngine::get()->get_world(); if (const auto controller = sdk::UGameplayStatics::get()->get_player_controller(world, 0); controller != nullptr) { @@ -5040,7 +5048,7 @@ __forceinline void FFakeStereoRenderingHook::calculate_stereo_view_offset( } // Modify Player Control Rotation - if (true_index == 1 && vr->is_aim_modify_player_control_rotation_enabled() && vr->is_any_aim_method_active()) { + if (is_last_eye_pass && vr->is_aim_modify_player_control_rotation_enabled() && vr->is_any_aim_method_active()) { if (g_hook->m_tracking_system_hook != nullptr) { g_hook->m_tracking_system_hook->manual_update_control_rotation(); } @@ -5166,7 +5174,7 @@ __forceinline Matrix4x4f* FFakeStereoRenderingHook::calculate_stereo_projection_ auto true_index = index_starts_from_one ? ((view_index + 1) % 2) : (view_index % 2); if (vr->is_using_afr()) { - true_index = g_frame_count % 2; + true_index = vr->is_using_mono() ? 0 : g_frame_count % 2; } auto& double_matrix = *(Matrix4x4d*)out; @@ -5352,7 +5360,7 @@ uint32_t FFakeStereoRenderingHook::get_desired_number_of_views_hook(FFakeStereoR if (!is_stereo_enabled || (vr->is_using_afr() && !vr->is_splitscreen_compatibility_enabled())) { // We need to know about the second scene state to fix ghosting, so set the view count to 2 // after we know about it, we can continue returning 1. - if (is_stereo_enabled && vr->is_ghosting_fix_enabled() && vr->is_using_afr() && + if (is_stereo_enabled && vr->is_ghosting_fix_enabled() && vr->is_using_alternating_eyes() && g_hook->m_sceneview_data.known_scene_states.size() < 2 && g_hook->m_fixed_localplayer_view_count && !!g_hook->m_sceneview_data.constructor_hook && g_hook->m_has_view_extensions_installed) { diff --git a/src/mods/vr/OverlayComponent.cpp b/src/mods/vr/OverlayComponent.cpp index 74b1574ab..c6bc424f2 100644 --- a/src/mods/vr/OverlayComponent.cpp +++ b/src/mods/vr/OverlayComponent.cpp @@ -97,7 +97,7 @@ void OverlayComponent::update_input_mouse_emulation() { if (m_framework_intersect_state.intersecting && VR::get()->is_using_controllers()) { auto vr = VR::get(); auto& io = ImGui::GetIO(); - const auto is_initial_frame = !vr->is_using_afr() || vr->get_frame_count() % 2 == vr->m_left_eye_interval; + const auto is_initial_frame = !vr->is_using_alternating_eyes() || vr->get_frame_count() % 2 == vr->m_left_eye_interval; const auto x = m_framework_intersect_state.swapchain_intersection_point.x; const auto y = m_framework_intersect_state.swapchain_intersection_point.y; @@ -249,7 +249,7 @@ void OverlayComponent::update_input_openvr() { auto vr = VR::get(); auto& io = ImGui::GetIO(); - const auto is_initial_frame = !vr->is_using_afr() || vr->get_frame_count() % 2 == vr->m_left_eye_interval; + const auto is_initial_frame = !vr->is_using_alternating_eyes() || vr->get_frame_count() % 2 == vr->m_left_eye_interval; // Restore the previous frame's input state memcpy(io.KeysDown, m_initial_imgui_input_state.KeysDown, sizeof(io.KeysDown)); diff --git a/src/mods/vr/runtimes/OpenXR.cpp b/src/mods/vr/runtimes/OpenXR.cpp index 2f4b3a997..96fc99f23 100644 --- a/src/mods/vr/runtimes/OpenXR.cpp +++ b/src/mods/vr/runtimes/OpenXR.cpp @@ -168,7 +168,7 @@ VRRuntime::Error OpenXR::synchronize_frame(std::optional frame_count) this->pipeline_states[next_frame].frame_state = this->frame_state; this->pipeline_states[next_frame].prev_frame_count = this->last_submit_state.frame_count; - if (VR::get()->is_using_afr()) { + if (VR::get()->is_using_alternating_eyes()) { const auto afr_frame = (next_frame + 1) % OpenXR::QUEUE_SIZE; this->pipeline_states[afr_frame].frame_state = this->frame_state; this->pipeline_states[afr_frame].prev_frame_count = next_frame; @@ -177,7 +177,7 @@ VRRuntime::Error OpenXR::synchronize_frame(std::optional frame_count) this->pipeline_states[*frame_count % OpenXR::QUEUE_SIZE].frame_state = this->frame_state; this->pipeline_states[*frame_count % OpenXR::QUEUE_SIZE].prev_frame_count = *frame_count; - if (VR::get()->is_using_afr()) { + if (VR::get()->is_using_alternating_eyes()) { this->pipeline_states[(*frame_count + 1) % OpenXR::QUEUE_SIZE].frame_state = this->frame_state; this->pipeline_states[(*frame_count + 1) % OpenXR::QUEUE_SIZE].prev_frame_count = *frame_count; }