Skip to content

Commit 03f6be2

Browse files
committed
Implement dynamic framerate
Reduce tracking frequency to head fps
1 parent 93a21e5 commit 03f6be2

File tree

15 files changed

+274
-51
lines changed

15 files changed

+274
-51
lines changed

alvr/client_core/src/c_api.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,14 @@ pub extern "C" fn alvr_get_tracker_prediction_offset_ns() -> u64 {
398398
}
399399

400400
#[no_mangle]
401-
pub extern "C" fn alvr_report_submit(target_timestamp_ns: u64, vsync_queue_ns: u64) {
401+
pub extern "C" fn alvr_report_submit(
402+
target_timestamp_ns: u64,
403+
frame_interval_ns: u64,
404+
vsync_queue_ns: u64,
405+
) {
402406
crate::report_submit(
403407
Duration::from_nanos(target_timestamp_ns),
408+
Duration::from_nanos(frame_interval_ns),
404409
Duration::from_nanos(vsync_queue_ns),
405410
);
406411
}

alvr/client_core/src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ pub fn get_tracker_prediction_offset() -> Duration {
197197
}
198198
}
199199

200-
pub fn report_submit(target_timestamp: Duration, vsync_queue: Duration) {
200+
pub fn report_submit(target_timestamp: Duration, frame_interval: Duration, vsync_queue: Duration) {
201201
if let Some(stats) = &mut *STATISTICS_MANAGER.lock() {
202-
stats.report_submit(target_timestamp, vsync_queue);
202+
stats.report_submit(target_timestamp, frame_interval, vsync_queue);
203203

204204
if let Some(sender) = &*STATISTICS_SENDER.lock() {
205205
if let Some(stats) = stats.summary(target_timestamp) {

alvr/client_core/src/statistics.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,15 @@ impl StatisticsManager {
9595
}
9696

9797
// vsync_queue is the latency between this call and the vsync. it cannot be measured by ALVR and
98-
// should be reported by the VR runtime
99-
pub fn report_submit(&mut self, target_timestamp: Duration, vsync_queue: Duration) {
98+
// should be reported by the VR runtime.
99+
// predicted_frame_interval is the frame interval returned by the runtime. this is more stable
100+
// any any interval mearued by us.
101+
pub fn report_submit(
102+
&mut self,
103+
target_timestamp: Duration,
104+
predicted_frame_interval: Duration,
105+
vsync_queue: Duration,
106+
) {
100107
let now = Instant::now();
101108

102109
if let Some(frame) = self
@@ -118,6 +125,8 @@ impl StatisticsManager {
118125
let vsync = now + vsync_queue;
119126
frame.client_stats.frame_interval = vsync.saturating_duration_since(self.prev_vsync);
120127
self.prev_vsync = vsync;
128+
129+
frame.client_stats.predicted_frame_interval = predicted_frame_interval
121130
}
122131
}
123132

alvr/client_mock/src/main.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ fn tracking_thread(streaming: Arc<RelaxedAtomic>, fps: f32, input: Arc<RwLock<Wi
196196

197197
drop(input_lock);
198198

199-
loop_deadline += Duration::from_secs_f32(1.0 / fps / 3.0);
199+
loop_deadline += Duration::from_secs_f32(1.0 / fps);
200200
thread::sleep(loop_deadline.saturating_duration_since(Instant::now()))
201201
}
202202
}
@@ -271,6 +271,7 @@ fn client_thread(
271271

272272
alvr_client_core::report_submit(
273273
window_output.current_frame_timestamp,
274+
Duration::from_secs_f32(1.0 / window_output.fps),
274275
Duration::from_millis(input_lock.emulated_vsync_ms),
275276
);
276277

alvr/client_openxr/src/lib.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,7 @@ pub fn entry_point() {
463463
let mut views_history = VecDeque::new();
464464

465465
let (history_view_sender, history_view_receiver) = mpsc::channel();
466+
let mut frame_interval_sender = None;
466467
let mut reference_space_sender = None::<mpsc::Sender<_>>;
467468

468469
let default_view = xr::View {
@@ -692,10 +693,13 @@ pub fn entry_point() {
692693

693694
let (sender, reference_space_receiver) = mpsc::channel();
694695
reference_space_sender = Some(sender);
696+
let (sender, frame_interval_receiver) = mpsc::channel();
697+
frame_interval_sender = Some(sender);
695698

696699
streaming_input_thread = Some(thread::spawn(move || {
697700
let mut deadline = Instant::now();
698-
let frame_interval = Duration::from_secs_f32(1.0 / refresh_rate_hint);
701+
let mut frame_interval =
702+
Duration::from_secs_f32(1.0 / refresh_rate_hint);
699703

700704
while is_streaming.value() {
701705
update_streaming_input(&mut context);
@@ -704,7 +708,11 @@ pub fn entry_point() {
704708
context.reference_space = reference_space;
705709
}
706710

707-
deadline += frame_interval / 3;
711+
if let Ok(interval) = frame_interval_receiver.try_recv() {
712+
frame_interval = interval;
713+
}
714+
715+
deadline += frame_interval;
708716
thread::sleep(deadline.saturating_duration_since(Instant::now()));
709717
}
710718
}));
@@ -832,6 +840,10 @@ pub fn entry_point() {
832840
let vsync_time =
833841
Duration::from_nanos(frame_state.predicted_display_time.as_nanos() as _);
834842

843+
if let Some(sender) = &frame_interval_sender {
844+
sender.send(frame_interval).ok();
845+
}
846+
835847
xr_frame_stream.begin().unwrap();
836848

837849
if !frame_state.should_render {
@@ -901,7 +913,11 @@ pub fn entry_point() {
901913

902914
if !hardware_buffer.is_null() {
903915
if let Some(now) = xr_runtime_now(&xr_instance) {
904-
alvr_client_core::report_submit(timestamp, vsync_time.saturating_sub(now));
916+
alvr_client_core::report_submit(
917+
timestamp,
918+
frame_interval,
919+
vsync_time.saturating_sub(now),
920+
);
905921
}
906922
}
907923

alvr/common/src/average.rs

+86-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
use std::{collections::VecDeque, time::Duration};
1+
use glam::Vec2;
2+
use std::{
3+
collections::VecDeque,
4+
f32::consts::PI,
5+
time::{Duration, Instant},
6+
};
27

38
pub struct SlidingWindowAverage<T> {
49
history_buffer: VecDeque<T>,
@@ -33,3 +38,83 @@ impl SlidingWindowAverage<Duration> {
3338
self.history_buffer.iter().sum::<Duration>() / self.history_buffer.len() as u32
3439
}
3540
}
41+
42+
pub enum DurationOffset {
43+
Positive(Duration),
44+
Negative(Duration),
45+
}
46+
47+
// Calculate average time phase. The average is calulated in the complex domain and returned back as
48+
// the phase time offset. Only the phase samples are stored (as a complex number), not the frame
49+
// interval, since it's useless for these calculations.
50+
pub struct SlidingTimePhaseAverage {
51+
last_time_sample: Instant,
52+
last_sample: Vec2,
53+
history_buffer: VecDeque<Vec2>,
54+
max_history_size: usize,
55+
}
56+
57+
impl SlidingTimePhaseAverage {
58+
pub fn new(max_history_size: usize) -> Self {
59+
Self {
60+
last_time_sample: Instant::now(),
61+
last_sample: Vec2::new(1.0, 0.0),
62+
history_buffer: [].into_iter().collect(),
63+
max_history_size,
64+
}
65+
}
66+
67+
// The sample is actually the time of this call.
68+
pub fn submit_sample(&mut self, frame_interval: Duration) {
69+
let time_sample = Instant::now();
70+
71+
let phase_sample = ((time_sample - self.last_time_sample).as_secs_f32()
72+
/ frame_interval.as_secs_f32())
73+
.fract()
74+
* 2.0
75+
* PI;
76+
77+
let complex_sample = Vec2::new(f32::cos(phase_sample), f32::sin(phase_sample));
78+
79+
if self.history_buffer.len() >= self.max_history_size {
80+
self.history_buffer.pop_front();
81+
}
82+
83+
self.history_buffer.push_back(complex_sample);
84+
85+
self.last_time_sample = time_sample;
86+
self.last_sample = complex_sample
87+
}
88+
89+
// The reference frame of the phase average is an implementation detail. This method returns the
90+
// phase offset wrt the time of this call.
91+
pub fn get_average_diff(&self, frame_interval: Duration) -> DurationOffset {
92+
let now = Instant::now();
93+
94+
let current_phase =
95+
((now - self.last_time_sample).as_secs_f32() / frame_interval.as_secs_f32()).fract()
96+
* 2.0
97+
* PI;
98+
99+
// let current_complex_phase = Vec2::new(f32::cos(current_phase), f32::sin(current_phase));
100+
101+
// Note: no need to normalize
102+
let average_complex = self.history_buffer.iter().sum::<Vec2>();
103+
let average_phase = f32::atan2(average_complex.y, average_complex.x);
104+
105+
let phase_diff = current_phase - average_phase;
106+
107+
// Nomalized between -PI and +PI
108+
let normalized_phase_diff = (phase_diff + PI).rem_euclid(2.0 * PI) - PI;
109+
110+
if normalized_phase_diff.is_sign_positive() {
111+
DurationOffset::Positive(Duration::from_secs_f32(
112+
normalized_phase_diff * frame_interval.as_secs_f32(),
113+
))
114+
} else {
115+
DurationOffset::Negative(Duration::from_secs_f32(
116+
-normalized_phase_diff * frame_interval.as_secs_f32(),
117+
))
118+
}
119+
}
120+
}

alvr/packets/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ pub struct ClientStatistics {
204204
pub rendering: Duration,
205205
pub vsync_queue: Duration,
206206
pub total_pipeline_latency: Duration,
207+
pub predicted_frame_interval: Duration,
207208
}
208209

209210
#[derive(Serialize, Deserialize, Clone, Debug)]

alvr/server/cpp/alvr_server/alvr_server.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,17 @@ void DeinitializeStreaming() {
226226
}
227227
}
228228

229-
void SendVSync() { vr::VRServerDriverHost()->VsyncEvent(0.0); }
230-
231229
void RequestIDR() {
232230
if (g_driver_provider.hmd && g_driver_provider.hmd->m_encoder) {
233231
g_driver_provider.hmd->m_encoder->InsertIDR();
234232
}
235233
}
236234

235+
// Linux-only
236+
void NotifyVsync() {
237+
// todo
238+
}
239+
237240
void SetTracking(unsigned long long targetTimestampNs,
238241
float controllerPoseTimeOffsetS,
239242
const FfiDeviceMotion *deviceMotions,

alvr/server/cpp/alvr_server/bindings.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ extern "C" void (*WaitForVSync)();
127127
extern "C" void *CppEntryPoint(const char *pInterfaceName, int *pReturnCode);
128128
extern "C" void InitializeStreaming();
129129
extern "C" void DeinitializeStreaming();
130-
extern "C" void SendVSync();
131130
extern "C" void RequestIDR();
131+
extern "C" void NotifyVsync();
132132
extern "C" void SetTracking(unsigned long long targetTimestampNs,
133133
float controllerPoseTimeOffsetS,
134134
const FfiDeviceMotion *deviceMotions,

alvr/server/src/connection.rs

+25-10
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ use crate::{
22
bitrate::BitrateManager,
33
buttons::BUTTON_PATH_FROM_ID,
44
face_tracking::FaceTrackingSink,
5-
haptics,
5+
haptics, openvr_props, phase_sync,
66
sockets::WelcomeSocket,
77
statistics::StatisticsManager,
88
tracking::{self, TrackingManager},
99
FfiButtonValue, FfiFov, FfiViewsConfig, VideoPacket, BITRATE_MANAGER, CONTROL_CHANNEL_SENDER,
10-
DECODER_CONFIG, DISCONNECT_CLIENT_NOTIFIER, HAPTICS_SENDER, RESTART_NOTIFIER,
11-
SERVER_DATA_MANAGER, STATISTICS_MANAGER, VIDEO_RECORDING_FILE, VIDEO_SENDER,
10+
DECODER_CONFIG, DISCONNECT_CLIENT_NOTIFIER, HAPTICS_SENDER, PHASE_SYNC_MANAGER,
11+
RESTART_NOTIFIER, SERVER_DATA_MANAGER, STATISTICS_MANAGER, VIDEO_RECORDING_FILE, VIDEO_SENDER,
1212
};
1313
use alvr_audio::AudioDevice;
1414
use alvr_common::{
@@ -24,7 +24,10 @@ use alvr_packets::{
2424
ButtonValue, ClientConnectionResult, ClientControlPacket, ClientListAction, ClientStatistics,
2525
ServerControlPacket, StreamConfigPacket, Tracking, AUDIO, HAPTICS, STATISTICS, TRACKING, VIDEO,
2626
};
27-
use alvr_session::{CodecType, ControllersEmulationMode, FrameSize, OpenvrConfig};
27+
use alvr_session::{
28+
CodecType, ControllersEmulationMode, FrameSize, OpenvrConfig, OpenvrPropValue,
29+
OpenvrPropertyKey,
30+
};
2831
use alvr_sockets::{
2932
spawn_cancelable, ControlSocketReceiver, ControlSocketSender, PeerType, ProtoControlSocket,
3033
StreamSocketBuilder, KEEPALIVE_INTERVAL,
@@ -636,8 +639,8 @@ async fn connection_pipeline(
636639
crate::SetOpenvrProperty(
637640
*alvr_common::HEAD_ID,
638641
crate::openvr_props::to_ffi_openvr_prop(
639-
alvr_session::OpenvrPropertyKey::AudioDefaultPlaybackDeviceId,
640-
alvr_session::OpenvrPropValue::String(id),
642+
OpenvrPropertyKey::AudioDefaultPlaybackDeviceId,
643+
OpenvrPropValue::String(id),
641644
),
642645
)
643646
}
@@ -660,8 +663,8 @@ async fn connection_pipeline(
660663
crate::SetOpenvrProperty(
661664
*alvr_common::HEAD_ID,
662665
crate::openvr_props::to_ffi_openvr_prop(
663-
alvr_session::OpenvrPropertyKey::AudioDefaultPlaybackDeviceId,
664-
alvr_session::OpenvrPropValue::String(id),
666+
OpenvrPropertyKey::AudioDefaultPlaybackDeviceId,
667+
OpenvrPropValue::String(id),
665668
),
666669
)
667670
}
@@ -685,8 +688,8 @@ async fn connection_pipeline(
685688
crate::SetOpenvrProperty(
686689
*alvr_common::HEAD_ID,
687690
crate::openvr_props::to_ffi_openvr_prop(
688-
alvr_session::OpenvrPropertyKey::AudioDefaultRecordingDeviceId,
689-
alvr_session::OpenvrPropValue::String(id),
691+
OpenvrPropertyKey::AudioDefaultRecordingDeviceId,
692+
OpenvrPropValue::String(id),
690693
),
691694
)
692695
}
@@ -916,6 +919,7 @@ async fn connection_pipeline(
916919
if let Some(stats) = &mut *STATISTICS_MANAGER.lock() {
917920
let timestamp = client_stats.target_timestamp;
918921
let decoder_latency = client_stats.video_decode;
922+
let predicted_frame_interval = client_stats.predicted_frame_interval;
919923
let network_latency = stats.report_statistics(client_stats);
920924

921925
BITRATE_MANAGER.lock().report_frame_latencies(
@@ -924,6 +928,17 @@ async fn connection_pipeline(
924928
network_latency,
925929
decoder_latency,
926930
);
931+
932+
let mut phase_sync_lock = PHASE_SYNC_MANAGER.lock();
933+
phase_sync_lock.report_predicted_frame_interval(predicted_frame_interval);
934+
935+
openvr_props::set_prop(
936+
*HEAD_ID,
937+
OpenvrPropertyKey::DisplayFrequency,
938+
OpenvrPropValue::Float(
939+
1.0 / phase_sync_lock.frame_interval_average().as_secs_f32(),
940+
),
941+
);
927942
}
928943
}
929944
}

0 commit comments

Comments
 (0)