ALVR-IPv6/alvr/session/src/settings.rs

646 lines
19 KiB
Rust

use bytemuck::{Pod, Zeroable};
use serde::{Deserialize, Serialize};
use settings_schema::{DictionaryDefault, EntryData, SettingsSchema, Switch, SwitchDefault};
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type", content = "content")]
pub enum FrameSize {
#[schema(min = 0.25, max = 2., step = 0.01)]
Scale(f32),
Absolute {
#[schema(min = 32, step = 32)]
width: u32,
#[schema(min = 32, step = 32)]
height: u32,
},
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
pub enum MediacodecDataType {
Float(f32),
Int32(i32),
Int64(i64),
String(String), // Note: Double, Rect and Size are for level 28 and not compatible with the Oculus Go
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoCoding {
codec: CodecType,
mediacodec_extra_options: Vec<(String, MediacodecDataType)>,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LatencyUseFrametimeDesc {
#[schema(advanced, min = 10000, max = 100000, step = 1000)]
pub latency_target_maximum: u64,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdaptiveBitrateDesc {
#[schema(min = 10, max = 500, step = 1)]
pub bitrate_maximum: u64,
#[schema(advanced, min = 1000, max = 25000, step = 500)]
pub latency_target: u64,
#[schema(advanced)]
pub latency_use_frametime: Switch<LatencyUseFrametimeDesc>,
#[schema(advanced, min = 500, max = 5000, step = 100)]
pub latency_threshold: u64,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FoveatedRenderingDesc {
#[schema(min = 0.5, max = 10., step = 0.1)]
pub strength: f32,
#[schema(advanced, min = 0.5, max = 2., step = 0.1)]
pub shape: f32,
#[schema(min = -0.05, max = 0.05, step = 0.001)]
pub vertical_offset: f32,
}
#[derive(SettingsSchema, Clone, Copy, Serialize, Deserialize, Pod, Zeroable)]
#[repr(C)]
pub struct ColorCorrectionDesc {
#[schema(min = -1., max = 1., step = 0.01)]
pub brightness: f32,
#[schema(min = -1., max = 1., step = 0.01)]
pub contrast: f32,
#[schema(min = -1., max = 1., step = 0.01)]
pub saturation: f32,
#[schema(min = 0., max = 5., step = 0.01)]
pub gamma: f32,
#[schema(min = -1., max = 5., step = 0.01)]
pub sharpening: f32,
}
// Note: This enum cannot be converted to camelCase due to a inconsistency between generation and
// validation: "hevc" vs "hEVC".
// This is caused by serde and settings-schema using different libraries for casing conversion
// todo: don't use casing conversion also for all other structs and enums
#[derive(SettingsSchema, Serialize, Deserialize, Debug, Copy, Clone)]
#[serde(tag = "type", content = "content")]
#[repr(u8)]
pub enum CodecType {
H264,
HEVC,
}
#[derive(SettingsSchema, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase", tag = "type", content = "content")]
#[repr(u8)]
pub enum TrackingSpace {
Local,
Stage,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoDesc {
#[schema(advanced)]
pub adapter_index: u32,
// Dropdown with 25%, 50%, 75%, 100%, 125%, 150% etc or custom
// Should set renderResolution (always in scale mode).
// When the user sets a resolution not obtainable with the preset scales, set the dropdown to
// custom.
// Warping compensation is already applied by the web server and driver
#[schema(placeholder = "resolution_dropdown")]
//
#[schema(advanced)]
pub render_resolution: FrameSize,
#[schema(advanced)]
pub recommended_target_resolution: FrameSize,
#[schema(placeholder = "display_refresh_rate")]
//
#[schema(advanced)]
pub preferred_fps: f32,
pub codec: CodecType,
#[schema(advanced)]
pub video_coding: VideoCoding,
#[schema(advanced)]
pub client_request_realtime_decoder: bool,
pub use_10bit_encoder: bool,
#[schema(min = 1, max = 500)]
pub encode_bitrate_mbs: u64,
pub adaptive_bitrate: Switch<AdaptiveBitrateDesc>,
#[schema(advanced)]
pub seconds_from_vsync_to_photons: f32,
pub foveated_rendering: Switch<FoveatedRenderingDesc>,
pub color_correction: Switch<ColorCorrectionDesc>,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase", tag = "type", content = "content")]
pub enum AudioDeviceId {
Default,
Name(String),
#[schema(min = 1, gui = "UpDown")]
Index(u64),
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AudioConfig {
#[schema(min = 0, max = 200)]
pub average_buffering_ms: u64,
#[schema(advanced, min = 1, max = 20)]
pub batch_ms: u64,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameAudioDesc {
#[schema(placeholder = "device_dropdown")]
//
#[schema(advanced)]
pub device_id: AudioDeviceId,
pub mute_when_streaming: bool,
pub config: AudioConfig,
}
// Note: sample rate is a free parameter for microphone, because both server and client supports
// resampling. In contrary, for game audio, the server does not support resampling.
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MicrophoneDesc {
#[schema(placeholder = "input_device_dropdown")]
//
#[schema(advanced)]
pub input_device_id: AudioDeviceId,
#[schema(placeholder = "output_device_dropdown")]
//
#[schema(advanced)]
pub output_device_id: AudioDeviceId,
#[schema(advanced)]
pub sample_rate: u32,
pub config: AudioConfig,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AudioSection {
pub game_audio: Switch<GameAudioDesc>,
pub microphone: Switch<MicrophoneDesc>,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ControllersDesc {
// Dropdown:
// Oculus Rift S
// Oculus Rift S (no handtracking pinch)
// Valve Index
// Valve Index (no handtracking pinch)
// modeIdx and the following strings must be set accordingly
#[schema(placeholder = "controller_mode")]
//
#[schema(advanced)]
pub mode_idx: i32,
#[schema(advanced)]
pub tracking_system_name: String,
#[schema(advanced)]
pub manufacturer_name: String,
#[schema(advanced)]
pub model_number: String,
#[schema(advanced)]
pub render_model_name_left: String,
#[schema(advanced)]
pub render_model_name_right: String,
#[schema(advanced)]
pub serial_number: String,
#[schema(advanced)]
pub ctrl_type_left: String,
#[schema(advanced)]
pub ctrl_type_right: String,
#[schema(advanced)]
pub registered_device_type: String,
#[schema(advanced)]
pub input_profile_path: String,
#[schema(placeholder = "tracking_speed")]
//
#[schema(advanced)]
pub pose_time_offset: f32,
#[schema(advanced)]
pub clientside_prediction: bool,
#[schema(advanced)]
pub position_offset_left: [f32; 3],
#[schema(advanced)]
pub rotation_offset_left: [f32; 3],
#[schema(min = 0., max = 5., step = 0.1)]
pub haptics_intensity: f32,
#[schema(advanced)]
pub use_headset_tracking_system: bool,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HeadsetDesc {
#[schema(advanced)]
pub mode_idx: u64,
#[schema(advanced)]
pub universe_id: u64,
// Oculus Rift S or HTC Vive. Should all the following strings accordingly
#[schema(placeholder = "headset_emulation_mode")]
//
#[schema(advanced)]
pub serial_number: String,
#[schema(advanced)]
pub tracking_system_name: String,
#[schema(advanced)]
pub model_number: String,
#[schema(advanced)]
pub driver_version: String,
#[schema(advanced)]
pub manufacturer_name: String,
#[schema(advanced)]
pub render_model_name: String,
#[schema(advanced)]
pub registered_device_type: String,
#[schema(advanced)]
pub tracking_frame_offset: i32,
#[schema(advanced)]
pub position_offset: [f32; 3],
pub force_3dof: bool,
#[schema(advanced)]
pub tracking_ref_only: bool,
#[schema(advanced)]
pub enable_vive_tracker_proxy: bool,
pub controllers: Switch<ControllersDesc>,
pub tracking_space: TrackingSpace,
#[schema(advanced)]
pub extra_latency_mode: bool,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type", content = "content")]
pub enum SocketProtocol {
Udp,
#[schema(advanced)]
#[serde(rename_all = "camelCase")]
ThrottledUdp {
#[schema(min = 1.0, step = 0.1, gui = "UpDown")]
bitrate_multiplier: f32,
},
Tcp,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DiscoveryConfig {
#[schema(advanced)]
pub auto_trust_clients: bool,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConnectionDesc {
pub client_discovery: Switch<DiscoveryConfig>,
#[schema(advanced, min = 1024, max = 65535)]
pub web_server_port: u16,
pub stream_protocol: SocketProtocol,
#[schema(advanced)]
pub stream_port: u16,
#[schema(advanced)]
pub aggressive_keyframe_resend: bool,
#[schema(advanced)]
pub on_connect_script: String,
#[schema(advanced)]
pub on_disconnect_script: String,
#[schema(advanced)]
pub enable_fec: bool,
}
#[derive(SettingsSchema, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "camelCase", tag = "type", content = "content")]
pub enum Theme {
SystemDefault,
Classic,
Darkly,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type", content = "content")]
pub enum UpdateChannel {
NoUpdates,
Stable,
Beta,
Nightly,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "type", content = "content")]
pub enum LogLevel {
Error,
Warning,
Info,
Debug,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExtraDesc {
pub theme: Theme,
pub client_dark_mode: bool,
pub revert_confirm_dialog: bool,
pub restart_confirm_dialog: bool,
pub prompt_before_update: bool,
pub update_channel: UpdateChannel,
pub log_to_disk: bool,
#[schema(advanced)]
pub notification_level: LogLevel,
#[schema(advanced)]
pub exclude_notifications_without_id: bool,
}
#[derive(SettingsSchema, Serialize, Deserialize)]
pub struct Settings {
pub video: VideoDesc,
pub audio: AudioSection,
pub headset: HeadsetDesc,
pub connection: ConnectionDesc,
pub extra: ExtraDesc,
}
pub fn session_settings_default() -> SettingsDefault {
SettingsDefault {
video: VideoDescDefault {
adapter_index: 0,
render_resolution: FrameSizeDefault {
variant: FrameSizeDefaultVariant::Scale,
Scale: 0.75,
Absolute: FrameSizeAbsoluteDefault {
width: 2880,
height: 1600,
},
},
recommended_target_resolution: FrameSizeDefault {
variant: FrameSizeDefaultVariant::Scale,
Scale: 0.75,
Absolute: FrameSizeAbsoluteDefault {
width: 2880,
height: 1600,
},
},
preferred_fps: 72.,
codec: CodecTypeDefault {
variant: CodecTypeDefaultVariant::H264,
},
video_coding: VideoCodingDefault {
codec: CodecTypeDefault {
variant: CodecTypeDefaultVariant::H264,
},
mediacodec_extra_options: DictionaryDefault {
key: "".into(),
value: MediacodecDataTypeDefault {
variant: MediacodecDataTypeDefaultVariant::String,
Float: 0.0,
Int32: 0,
Int64: 0,
String: "".into(),
},
content: vec![
("operating-rate".into(), MediacodecDataType::Int32(i32::MAX)),
("priority".into(), MediacodecDataType::Int32(0)),
// low-latency: only applicable on API level 30. Quest 1 and 2 might not be
// cabable, since they are on level 29.
("low-latency".into(), MediacodecDataType::Int32(1)),
(
"vendor.qti-ext-dec-low-latency.enable".into(),
MediacodecDataType::Int32(1),
),
],
},
},
client_request_realtime_decoder: true,
use_10bit_encoder: false,
encode_bitrate_mbs: 30,
adaptive_bitrate: SwitchDefault {
enabled: !cfg!(target_os = "linux"),
content: AdaptiveBitrateDescDefault {
bitrate_maximum: 200,
latency_target: 12000,
latency_use_frametime: SwitchDefault {
enabled: true,
content: LatencyUseFrametimeDescDefault {
latency_target_maximum: 50000,
},
},
latency_threshold: 4000,
},
},
seconds_from_vsync_to_photons: 0.005,
foveated_rendering: SwitchDefault {
enabled: !cfg!(target_os = "linux"),
content: FoveatedRenderingDescDefault {
strength: 2.,
shape: 1.5,
vertical_offset: 0.,
},
},
color_correction: SwitchDefault {
enabled: false,
content: ColorCorrectionDescDefault {
brightness: 0.,
contrast: 0.,
saturation: 0.,
gamma: 1.,
sharpening: 0.,
},
},
},
audio: AudioSectionDefault {
game_audio: SwitchDefault {
enabled: !cfg!(target_os = "linux"),
content: GameAudioDescDefault {
device_id: AudioDeviceIdDefault {
variant: AudioDeviceIdDefaultVariant::Default,
Name: "".into(),
Index: 1,
},
mute_when_streaming: true,
config: AudioConfigDefault {
average_buffering_ms: 50,
batch_ms: 10,
},
},
},
microphone: SwitchDefault {
enabled: false,
content: MicrophoneDescDefault {
input_device_id: AudioDeviceIdDefault {
variant: AudioDeviceIdDefaultVariant::Default,
Name: "".into(),
Index: 1,
},
output_device_id: AudioDeviceIdDefault {
variant: AudioDeviceIdDefaultVariant::Default,
Name: "".into(),
Index: 1,
},
sample_rate: 44100,
config: AudioConfigDefault {
average_buffering_ms: 50,
batch_ms: 10,
},
},
},
},
headset: HeadsetDescDefault {
mode_idx: 2,
universe_id: 2,
serial_number: "1WMGH000XX0000".into(),
tracking_system_name: "oculus".into(),
model_number: "Miramar".into(),
driver_version: "1.55.0".into(),
manufacturer_name: "Oculus".into(),
render_model_name: "generic_hmd".into(),
registered_device_type: "oculus/1WMGH000XX0000".into(),
tracking_frame_offset: 0,
position_offset: [0., 0., 0.],
force_3dof: false,
tracking_ref_only: false,
enable_vive_tracker_proxy: false,
controllers: SwitchDefault {
enabled: true,
content: ControllersDescDefault {
mode_idx: 7,
tracking_system_name: "oculus".into(),
manufacturer_name: "Oculus".into(),
model_number: "Miramar".into(),
render_model_name_left: "oculus_quest2_controller_left".into(),
render_model_name_right: "oculus_quest2_controller_right".into(),
serial_number: "1WMGH000XX0000_Controller".into(),
ctrl_type_left: "oculus_touch".into(),
ctrl_type_right: "oculus_touch".into(),
registered_device_type: "oculus/1WMGH000XX0000_Controller".into(),
input_profile_path: "{oculus}/input/touch_profile.json".into(),
pose_time_offset: 0.01,
clientside_prediction: true,
position_offset_left: [-0.007, 0.005, -0.053],
rotation_offset_left: [36., 0., 0.],
haptics_intensity: 1.,
use_headset_tracking_system: false,
},
},
tracking_space: TrackingSpaceDefault {
variant: TrackingSpaceDefaultVariant::Local,
},
extra_latency_mode: false,
},
connection: ConnectionDescDefault {
client_discovery: SwitchDefault {
enabled: true,
content: DiscoveryConfigDefault {
auto_trust_clients: cfg!(debug_assertions),
},
},
web_server_port: 8082,
stream_protocol: SocketProtocolDefault {
variant: if !cfg!(target_os = "linux") {
SocketProtocolDefaultVariant::Udp
} else {
SocketProtocolDefaultVariant::Tcp
},
ThrottledUdp: SocketProtocolThrottledUdpDefault {
bitrate_multiplier: 1.5,
},
},
stream_port: 9944,
aggressive_keyframe_resend: false,
on_connect_script: "".into(),
on_disconnect_script: "".into(),
enable_fec: true,
},
extra: ExtraDescDefault {
theme: ThemeDefault {
variant: ThemeDefaultVariant::SystemDefault,
},
client_dark_mode: false,
revert_confirm_dialog: true,
restart_confirm_dialog: true,
prompt_before_update: true,
update_channel: UpdateChannelDefault {
variant: UpdateChannelDefaultVariant::Stable,
},
log_to_disk: cfg!(debug_assertions),
notification_level: LogLevelDefault {
variant: if cfg!(debug_assertions) {
LogLevelDefaultVariant::Info
} else {
LogLevelDefaultVariant::Warning
},
},
exclude_notifications_without_id: false,
},
}
}