diff --git a/alvr/client_core/src/c_api.rs b/alvr/client_core/src/c_api.rs index 39faa1628d..269c95c3b3 100644 --- a/alvr/client_core/src/c_api.rs +++ b/alvr/client_core/src/c_api.rs @@ -398,9 +398,14 @@ pub extern "C" fn alvr_get_tracker_prediction_offset_ns() -> u64 { } #[no_mangle] -pub extern "C" fn alvr_report_submit(target_timestamp_ns: u64, vsync_queue_ns: u64) { +pub extern "C" fn alvr_report_submit( + target_timestamp_ns: u64, + frame_interval_ns: u64, + vsync_queue_ns: u64, +) { crate::report_submit( Duration::from_nanos(target_timestamp_ns), + Duration::from_nanos(frame_interval_ns), Duration::from_nanos(vsync_queue_ns), ); } diff --git a/alvr/client_core/src/lib.rs b/alvr/client_core/src/lib.rs index 66bf2a57c1..f1133c4586 100644 --- a/alvr/client_core/src/lib.rs +++ b/alvr/client_core/src/lib.rs @@ -197,9 +197,9 @@ pub fn get_tracker_prediction_offset() -> Duration { } } -pub fn report_submit(target_timestamp: Duration, vsync_queue: Duration) { +pub fn report_submit(target_timestamp: Duration, frame_interval: Duration, vsync_queue: Duration) { if let Some(stats) = &mut *STATISTICS_MANAGER.lock() { - stats.report_submit(target_timestamp, vsync_queue); + stats.report_submit(target_timestamp, frame_interval, vsync_queue); if let Some(sender) = &*STATISTICS_SENDER.lock() { if let Some(stats) = stats.summary(target_timestamp) { diff --git a/alvr/client_core/src/statistics.rs b/alvr/client_core/src/statistics.rs index af9791d06e..0333d9d0e5 100644 --- a/alvr/client_core/src/statistics.rs +++ b/alvr/client_core/src/statistics.rs @@ -95,8 +95,15 @@ impl StatisticsManager { } // vsync_queue is the latency between this call and the vsync. it cannot be measured by ALVR and - // should be reported by the VR runtime - pub fn report_submit(&mut self, target_timestamp: Duration, vsync_queue: Duration) { + // should be reported by the VR runtime. + // predicted_frame_interval is the frame interval returned by the runtime. this is more stable + // any any interval mearued by us. + pub fn report_submit( + &mut self, + target_timestamp: Duration, + predicted_frame_interval: Duration, + vsync_queue: Duration, + ) { let now = Instant::now(); if let Some(frame) = self @@ -118,6 +125,8 @@ impl StatisticsManager { let vsync = now + vsync_queue; frame.client_stats.frame_interval = vsync.saturating_duration_since(self.prev_vsync); self.prev_vsync = vsync; + + frame.client_stats.predicted_frame_interval = predicted_frame_interval } } diff --git a/alvr/client_mock/src/main.rs b/alvr/client_mock/src/main.rs index 4e84dd1404..83938d23aa 100644 --- a/alvr/client_mock/src/main.rs +++ b/alvr/client_mock/src/main.rs @@ -196,7 +196,7 @@ fn tracking_thread(streaming: Arc, fps: f32, input: Arc>; let default_view = xr::View { @@ -693,10 +694,13 @@ pub fn entry_point() { let (sender, reference_space_receiver) = mpsc::channel(); reference_space_sender = Some(sender); + let (sender, frame_interval_receiver) = mpsc::channel(); + frame_interval_sender = Some(sender); streaming_input_thread = Some(thread::spawn(move || { let mut deadline = Instant::now(); - let frame_interval = Duration::from_secs_f32(1.0 / refresh_rate_hint); + let mut frame_interval = + Duration::from_secs_f32(1.0 / refresh_rate_hint); while is_streaming.value() { update_streaming_input(&mut context); @@ -705,7 +709,11 @@ pub fn entry_point() { context.reference_space = reference_space; } - deadline += frame_interval / 3; + if let Ok(interval) = frame_interval_receiver.try_recv() { + frame_interval = interval; + } + + deadline += frame_interval; thread::sleep(deadline.saturating_duration_since(Instant::now())); } })); @@ -833,6 +841,10 @@ pub fn entry_point() { let vsync_time = Duration::from_nanos(frame_state.predicted_display_time.as_nanos() as _); + if let Some(sender) = &frame_interval_sender { + sender.send(frame_interval).ok(); + } + xr_frame_stream.begin().unwrap(); if !frame_state.should_render { @@ -909,7 +921,11 @@ pub fn entry_point() { if !hardware_buffer.is_null() { if let Some(now) = xr_runtime_now(&xr_instance) { - alvr_client_core::report_submit(timestamp, vsync_time.saturating_sub(now)); + alvr_client_core::report_submit( + timestamp, + frame_interval, + vsync_time.saturating_sub(now), + ); } } diff --git a/alvr/packets/src/lib.rs b/alvr/packets/src/lib.rs index c14928a2fb..a8e1260b89 100644 --- a/alvr/packets/src/lib.rs +++ b/alvr/packets/src/lib.rs @@ -204,6 +204,7 @@ pub struct ClientStatistics { pub rendering: Duration, pub vsync_queue: Duration, pub total_pipeline_latency: Duration, + pub predicted_frame_interval: Duration, } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/alvr/server/cpp/alvr_server/alvr_server.cpp b/alvr/server/cpp/alvr_server/alvr_server.cpp index c84042dd89..4e6e2400d9 100644 --- a/alvr/server/cpp/alvr_server/alvr_server.cpp +++ b/alvr/server/cpp/alvr_server/alvr_server.cpp @@ -227,8 +227,6 @@ void DeinitializeStreaming() { } } -void SendVSync() { vr::VRServerDriverHost()->VsyncEvent(0.0); } - void RequestIDR() { if (g_driver_provider.hmd && g_driver_provider.hmd->m_encoder) { g_driver_provider.hmd->m_encoder->InsertIDR(); diff --git a/alvr/server/cpp/alvr_server/bindings.h b/alvr/server/cpp/alvr_server/bindings.h index 84da272c58..972d9e39d9 100644 --- a/alvr/server/cpp/alvr_server/bindings.h +++ b/alvr/server/cpp/alvr_server/bindings.h @@ -128,7 +128,6 @@ extern "C" void (*WaitForVSync)(); extern "C" void *CppEntryPoint(const char *pInterfaceName, int *pReturnCode); extern "C" void InitializeStreaming(); extern "C" void DeinitializeStreaming(); -extern "C" void SendVSync(); extern "C" void RequestIDR(); extern "C" void SetTracking(unsigned long long targetTimestampNs, float controllerPoseTimeOffsetS, diff --git a/alvr/server/src/lib.rs b/alvr/server/src/lib.rs index 1061996b97..d6da82b7d7 100644 --- a/alvr/server/src/lib.rs +++ b/alvr/server/src/lib.rs @@ -28,7 +28,7 @@ use alvr_common::{ once_cell::sync::{Lazy, OnceCell}, parking_lot::{Mutex, RwLock}, prelude::*, - RelaxedAtomic, + RelaxedAtomic, HEAD_ID, }; use alvr_events::EventType; use alvr_filesystem::{self as afs, Layout}; @@ -36,7 +36,7 @@ use alvr_packets::{ ClientListAction, DecoderInitializationConfig, Haptics, ServerControlPacket, VideoPacketHeader, }; use alvr_server_io::ServerDataManager; -use alvr_session::CodecType; +use alvr_session::{CodecType, OpenvrPropValue, OpenvrPropertyKey}; use bitrate::BitrateManager; use statistics::StatisticsManager; use std::{ @@ -483,13 +483,14 @@ pub unsafe extern "C" fn HmdDriverFactory( } extern "C" fn wait_for_vsync() { - let wait_duration = STATISTICS_MANAGER - .lock() - .as_mut() - .map(|stats| stats.duration_until_next_vsync()); + if let Some(stats) = &mut *STATISTICS_MANAGER.lock() { + openvr_props::set_prop( + *HEAD_ID, + OpenvrPropertyKey::DisplayFrequency, + OpenvrPropValue::Float(1.0 / stats.get_frame_interval().as_secs_f32()), + ); - if let Some(duration) = wait_duration { - thread::sleep(duration); + thread::sleep(stats.duration_until_next_vsync()); } } diff --git a/alvr/server/src/openvr_props.rs b/alvr/server/src/openvr_props.rs index f1db5be3c1..def449ce56 100644 --- a/alvr/server/src/openvr_props.rs +++ b/alvr/server/src/openvr_props.rs @@ -2,7 +2,7 @@ // todo: fill out more properties for headset and controllers // todo: add more emulation modes -use crate::{FfiOpenvrProperty, FfiOpenvrPropertyValue, SERVER_DATA_MANAGER}; +use crate::{openvr_props, FfiOpenvrProperty, FfiOpenvrPropertyValue, SERVER_DATA_MANAGER}; use alvr_common::{prelude::*, settings_schema::Switch, HEAD_ID, LEFT_HAND_ID, RIGHT_HAND_ID}; use alvr_session::{ ControllersEmulationMode, HeadsetEmulationMode, OpenvrPropValue, @@ -13,6 +13,12 @@ use std::{ ptr, }; +pub fn set_prop(device_id: u64, key: OpenvrPropertyKey, value: OpenvrPropValue) { + unsafe { + crate::SetOpenvrProperty(device_id, to_ffi_openvr_prop(key, value)); + } +} + pub fn to_ffi_openvr_prop(key: OpenvrPropertyKey, value: OpenvrPropValue) -> FfiOpenvrProperty { let type_ = match value { OpenvrPropValue::Bool(_) => crate::FfiOpenvrPropertyType_Bool, @@ -113,9 +119,7 @@ pub extern "C" fn set_device_openvr_props(device_id: u64) { if device_id == *HEAD_ID { fn set_prop(key: OpenvrPropertyKey, value: OpenvrPropValue) { info!("Setting head OpenVR prop: {key:?} => {value:?}"); - unsafe { - crate::SetOpenvrProperty(*HEAD_ID, to_ffi_openvr_prop(key, value)); - } + openvr_props::set_prop(*HEAD_ID, key, value); } fn set_string(key: OpenvrPropertyKey, value: &str) { set_prop(key, OpenvrPropValue::String(value.into())); diff --git a/alvr/server/src/statistics.rs b/alvr/server/src/statistics.rs index 87aa9a2246..82c6bb6b8c 100644 --- a/alvr/server/src/statistics.rs +++ b/alvr/server/src/statistics.rs @@ -11,6 +11,7 @@ const FULL_REPORT_INTERVAL: Duration = Duration::from_millis(500); pub struct HistoryFrame { target_timestamp: Duration, tracking_received: Instant, + // tracking_submitted: Instant, frame_present: Instant, frame_composed: Instant, frame_encoded: Instant, @@ -23,6 +24,7 @@ impl Default for HistoryFrame { Self { target_timestamp: Duration::ZERO, tracking_received: now, + // tracking_submitted: now, frame_present: now, frame_composed: now, frame_encoded: now, @@ -46,8 +48,8 @@ pub struct StatisticsManager { battery_gauges: HashMap, steamvr_pipeline_latency: Duration, total_pipeline_latency_average: SlidingWindowAverage, + predicted_frame_interval_average: SlidingWindowAverage, last_vsync_time: Instant, - frame_interval: Duration, } impl StatisticsManager { @@ -78,7 +80,10 @@ impl StatisticsManager { max_history_size, ), last_vsync_time: Instant::now(), - frame_interval: nominal_server_frame_interval, + predicted_frame_interval_average: SlidingWindowAverage::new( + nominal_server_frame_interval, + max_history_size, + ), } } @@ -100,6 +105,16 @@ impl StatisticsManager { } } + // pub fn report_tracking_submitted(&mut self, target_timestamp: Duration) { + // if let Some(frame) = self + // .history_buffer + // .iter_mut() + // .find(|frame| frame.target_timestamp == target_timestamp) + // { + // frame.tracking_submitted = Instant::now(); + // } + // } + pub fn report_frame_present(&mut self, target_timestamp: Duration, offset: Duration) { if let Some(frame) = self .history_buffer @@ -155,6 +170,9 @@ impl StatisticsManager { // Called every frame. Some statistics are reported once every frame // Returns network latency pub fn report_statistics(&mut self, client_stats: ClientStatistics) -> Duration { + self.predicted_frame_interval_average + .submit_sample(client_stats.predicted_frame_interval); + if let Some(frame) = self .history_buffer .iter_mut() @@ -275,15 +293,21 @@ impl StatisticsManager { .saturating_sub(self.total_pipeline_latency_average.get_average()) } + pub fn get_frame_interval(&self) -> Duration { + self.predicted_frame_interval_average.get_average() + } + // NB: this call is non-blocking, waiting should be done externally pub fn duration_until_next_vsync(&mut self) -> Duration { let now = Instant::now(); + let frame_interval = self.predicted_frame_interval_average.get_average(); + // update the last vsync if it's too old - while self.last_vsync_time + self.frame_interval < now { - self.last_vsync_time += self.frame_interval; + while self.last_vsync_time + frame_interval < now { + self.last_vsync_time += frame_interval; } - (self.last_vsync_time + self.frame_interval).saturating_duration_since(now) + (self.last_vsync_time + frame_interval).saturating_duration_since(now) } }