diff --git a/src/mods/VR.cpp b/src/mods/VR.cpp index 1053874..f45d15c 100644 --- a/src/mods/VR.cpp +++ b/src/mods/VR.cpp @@ -1160,6 +1160,9 @@ void VR::on_draw_ui() { m_openxr->resolution_scale = m_resolution_scale->value(); } m_use_async_aer->draw("Use Async AER"); + if (ImGui::IsItemDeactivatedAfterEdit()) { + get_runtime()->async_aer = m_use_async_aer->value(); + } m_flat_screen_distance->draw("Flat Screen Distance"); if (ImGui::IsItemDeactivatedAfterEdit()) @@ -1306,6 +1309,7 @@ void VR::on_config_load(const utility::Config& cfg, bool set_defaults) { get_runtime()->m_vertical_fov_scale = m_vertical_fov_scale->value(); get_runtime()->m_extended_fov_range = m_extended_fov_rage->value(); get_runtime()->m_flat_screen_distance = m_flat_screen_distance->value(); + get_runtime()->async_aer = m_use_async_aer->value(); } m_overlay_component.on_config_load(cfg, set_defaults); diff --git a/src/mods/VR.hpp b/src/mods/VR.hpp index 8a56940..ea95ad7 100644 --- a/src/mods/VR.hpp +++ b/src/mods/VR.hpp @@ -136,7 +136,7 @@ class VR : public Mod { // int32_t get_game_frame_count() const; bool is_using_async_aer() const { - return m_runtime->is_openxr() && m_use_async_aer->value(); + return m_runtime->async_aer; } bool is_gui_enabled() const { diff --git a/src/mods/vr/D3D11Component.cpp b/src/mods/vr/D3D11Component.cpp index 25b0a7c..51a11ba 100644 --- a/src/mods/vr/D3D11Component.cpp +++ b/src/mods/vr/D3D11Component.cpp @@ -302,21 +302,46 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) { } if (runtime->is_openvr()) { + // Calculate which frame each eye was actually rendered at (matching OpenXR's AFR logic) + auto l_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count + : vr->m_presenter_frame_count - 1; + auto r_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count - 1 + : vr->m_presenter_frame_count; + + // Submit newly rendered left eye with its pose vr::VRTextureWithPose_t left_eye{}; left_eye.handle = (void*)m_left_eye_tex.Get(); left_eye.eType = vr::TextureType_DirectX; left_eye.eColorSpace = vr::ColorSpace_Auto; - left_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(vr->m_presenter_frame_count); + left_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(l_frame); auto e = vr::VRCompositor()->Submit(vr::Eye_Left, (vr::Texture_t*)&left_eye, &vr->m_left_bounds, vr::Submit_TextureWithPose); - bool submitted = true; - if (e != vr::VRCompositorError_None) { spdlog::error("[VR] VRCompositor failed to submit left eye: {}", (int)e); vr->m_submitted = false; return e; } + + // Resubmit previous right eye frame with its original pose for runtime reprojection + // Skip on first frame when there's no previous frame to resubmit + if (vr->m_presenter_frame_count > 0) { + vr::VRTextureWithPose_t right_eye{}; + right_eye.handle = (void*)m_right_eye_tex.Get(); + right_eye.eType = vr::TextureType_DirectX; + right_eye.eColorSpace = vr::ColorSpace_Auto; + right_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(r_frame); + + e = vr::VRCompositor()->Submit(vr::Eye_Right, (vr::Texture_t*)&right_eye, &vr->m_right_bounds, vr::Submit_TextureWithPose); + + if (e != vr::VRCompositorError_None) { + spdlog::error("[VR] VRCompositor failed to submit right eye (resubmit): {}", (int)e); + } + } + + vr->m_submitted = true; } } else { auto copy_from_tex = backbufferIs8Bit ? backbuffer : m_right_eye_rt.tex; @@ -394,23 +419,46 @@ vr::EVRCompositorError D3D11Component::on_frame(VR* vr) { if (backbufferIs8Bit) { context->CopyResource(m_right_eye_tex.Get(), backbuffer.Get()); } - + + // Calculate which frame each eye was actually rendered at (matching OpenXR's AFR logic) + auto l_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count + : vr->m_presenter_frame_count - 1; + auto r_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count - 1 + : vr->m_presenter_frame_count; + + // Submit newly rendered right eye with its pose vr::VRTextureWithPose_t right_eye{}; right_eye.handle = (void*)m_right_eye_tex.Get(); right_eye.eType = vr::TextureType_DirectX; right_eye.eColorSpace = vr::ColorSpace_Auto; - right_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(vr->m_presenter_frame_count); + right_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(r_frame); auto e = vr::VRCompositor()->Submit(vr::Eye_Right, (vr::Texture_t*)&right_eye, &vr->m_right_bounds, vr::Submit_TextureWithPose); - bool submitted = true; - if (e != vr::VRCompositorError_None) { spdlog::error("[VR] VRCompositor failed to submit right eye: {}", (int)e); vr->m_submitted = false; return e; } + // Resubmit previous left eye frame with its original pose for runtime reprojection + // Skip on first frame when there's no previous frame to resubmit + if (vr->m_presenter_frame_count > 0) { + vr::VRTextureWithPose_t left_eye{}; + left_eye.handle = (void*)m_left_eye_tex.Get(); + left_eye.eType = vr::TextureType_DirectX; + left_eye.eColorSpace = vr::ColorSpace_Auto; + left_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(l_frame); + + e = vr::VRCompositor()->Submit(vr::Eye_Left, (vr::Texture_t*)&left_eye, &vr->m_left_bounds, vr::Submit_TextureWithPose); + + if (e != vr::VRCompositorError_None) { + spdlog::error("[VR] VRCompositor failed to submit left eye (resubmit): {}", (int)e); + } + } + vr->m_submitted = true; } diff --git a/src/mods/vr/D3D12Component.cpp b/src/mods/vr/D3D12Component.cpp index 155c3bf..93c6905 100644 --- a/src/mods/vr/D3D12Component.cpp +++ b/src/mods/vr/D3D12Component.cpp @@ -97,17 +97,26 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { if (runtime->is_openvr()) { m_openvr.copy_left(eye_texture.Get()); + // Calculate which frame each eye was actually rendered at (matching OpenXR's AFR logic) + auto l_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count + : vr->m_presenter_frame_count - 1; + auto r_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count - 1 + : vr->m_presenter_frame_count; + vr::D3D12TextureData_t left_tex { m_openvr.get_left().texture.Get(), command_queue, 0 }; + // Submit newly rendered left eye with its pose vr::VRTextureWithPose_t left_eye{}; left_eye.handle = (void*)&left_tex; left_eye.eType = vr::TextureType_DirectX12; left_eye.eColorSpace = vr::ColorSpace_Auto; - left_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(vr->m_presenter_frame_count); + left_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(l_frame); const auto left_bounds = vr::VRTextureBounds_t{runtime->view_bounds[0][0], runtime->view_bounds[0][2], runtime->view_bounds[0][1], runtime->view_bounds[0][3]}; @@ -117,6 +126,32 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { spdlog::error("[VR] VRCompositor failed to submit left eye: {}", (int)e); return e; } + + // Resubmit previous right eye frame with its original pose for runtime reprojection + // Skip on first frame when there's no previous frame to resubmit + if (vr->m_presenter_frame_count > 0) { + vr::D3D12TextureData_t right_tex { + m_openvr.get_right().texture.Get(), + command_queue, + 0 + }; + + vr::VRTextureWithPose_t right_eye{}; + right_eye.handle = (void*)&right_tex; + right_eye.eType = vr::TextureType_DirectX12; + right_eye.eColorSpace = vr::ColorSpace_Auto; + right_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(r_frame); + + const auto right_bounds = vr::VRTextureBounds_t{runtime->view_bounds[1][0], runtime->view_bounds[1][2], + runtime->view_bounds[1][1], runtime->view_bounds[1][3]}; + e = vr::VRCompositor()->Submit(vr::Eye_Right, (vr::Texture_t*)&right_eye, &right_bounds, vr::Submit_TextureWithPose); + + if (e != vr::VRCompositorError_None) { + spdlog::error("[VR] VRCompositor failed to submit right eye (resubmit): {}", (int)e); + } + } + + vr->m_submitted = true; } } else { // OpenXR texture @@ -140,17 +175,26 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { if (runtime->is_openvr()) { m_openvr.copy_right(eye_texture.Get()); + // Calculate which frame each eye was actually rendered at (matching OpenXR's AFR logic) + auto l_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count + : vr->m_presenter_frame_count - 1; + auto r_frame = vr->m_presenter_frame_count % 2 == vr->m_left_eye_interval + ? vr->m_presenter_frame_count - 1 + : vr->m_presenter_frame_count; + vr::D3D12TextureData_t right_tex { m_openvr.get_right().texture.Get(), command_queue, 0 }; + // Submit newly rendered right eye with its pose vr::VRTextureWithPose_t right_eye{}; right_eye.handle = (void*)&right_tex; right_eye.eType = vr::TextureType_DirectX12; right_eye.eColorSpace = vr::ColorSpace_Auto; - right_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(vr->m_presenter_frame_count); + right_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(r_frame); const auto right_bounds = vr::VRTextureBounds_t{runtime->view_bounds[1][0], runtime->view_bounds[1][2], runtime->view_bounds[1][1], runtime->view_bounds[1][3]}; @@ -159,10 +203,33 @@ vr::EVRCompositorError D3D12Component::on_frame(VR* vr) { if (e != vr::VRCompositorError_None) { spdlog::error("[VR] VRCompositor failed to submit right eye: {}", (int)e); return e; - } else { - vr->m_submitted = true; } + // Resubmit previous left eye frame with its original pose for runtime reprojection + // Skip on first frame when there's no previous frame to resubmit + if (vr->m_presenter_frame_count > 0) { + vr::D3D12TextureData_t left_tex { + m_openvr.get_left().texture.Get(), + command_queue, + 0 + }; + + vr::VRTextureWithPose_t left_eye{}; + left_eye.handle = (void*)&left_tex; + left_eye.eType = vr::TextureType_DirectX12; + left_eye.eColorSpace = vr::ColorSpace_Auto; + left_eye.mDeviceToAbsoluteTracking = GlobalPool::get_openvr_pose(l_frame); + + const auto left_bounds = vr::VRTextureBounds_t{runtime->view_bounds[0][0], runtime->view_bounds[0][2], + runtime->view_bounds[0][1], runtime->view_bounds[0][3]}; + e = vr::VRCompositor()->Submit(vr::Eye_Left, (vr::Texture_t*)&left_eye, &left_bounds, vr::Submit_TextureWithPose); + + if (e != vr::VRCompositorError_None) { + spdlog::error("[VR] VRCompositor failed to submit left eye (resubmit): {}", (int)e); + } + } + + vr->m_submitted = true; ++m_openvr.texture_counter; } } diff --git a/src/mods/vr/runtimes/OpenXR.hpp b/src/mods/vr/runtimes/OpenXR.hpp index 773d530..3fc1ec7 100644 --- a/src/mods/vr/runtimes/OpenXR.hpp +++ b/src/mods/vr/runtimes/OpenXR.hpp @@ -176,7 +176,6 @@ struct OpenXR final : public VRRuntime { bool session_ready{false}; bool frame_began{false}; bool frame_synced{false}; - bool async_aer{true}; #ifdef DEBUG_PROFILING_ENABLED bool profile_calls{true}; #else diff --git a/src/mods/vr/runtimes/VRRuntime.hpp b/src/mods/vr/runtimes/VRRuntime.hpp index c2592dc..cedc7bd 100644 --- a/src/mods/vr/runtimes/VRRuntime.hpp +++ b/src/mods/vr/runtimes/VRRuntime.hpp @@ -132,6 +132,7 @@ struct VRRuntime { bool got_first_sync{false}; bool handle_pause{false}; bool wants_reset_origin{true}; + bool async_aer{true}; // Alternate Frame Rendering - submit both eyes each present with historical poses std::optional error{};