Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/mods/UObjectHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
16 changes: 10 additions & 6 deletions src/mods/VR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
Expand Down
28 changes: 26 additions & 2 deletions src/mods/VR.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class VR : public Mod {
NATIVE_STEREO = 0,
SYNCHRONIZED = 1,
ALTERNATING = 2,
MONO = 3,
};

enum SynchronizeStage {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -837,6 +860,7 @@ class VR : public Mod {
"Native Stereo",
"Synchronized Sequential",
"Alternating/AFR",
"Mono",
};

static const inline std::vector<std::string> s_sync_mode_names{
Expand Down
17 changes: 13 additions & 4 deletions src/mods/vr/D3D11Component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
20 changes: 16 additions & 4 deletions src/mods/vr/D3D12Component.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
28 changes: 18 additions & 10 deletions src/mods/vr/FFakeStereoRenderingHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<double>*)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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down
4 changes: 2 additions & 2 deletions src/mods/vr/OverlayComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
Expand Down
4 changes: 2 additions & 2 deletions src/mods/vr/runtimes/OpenXR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ VRRuntime::Error OpenXR::synchronize_frame(std::optional<uint32_t> 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;
Expand All @@ -177,7 +177,7 @@ VRRuntime::Error OpenXR::synchronize_frame(std::optional<uint32_t> 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;
}
Expand Down
Loading