diff --git a/alvr/client_core/src/c_api.rs b/alvr/client_core/src/c_api.rs index 71bdf9974a..564a086653 100644 --- a/alvr/client_core/src/c_api.rs +++ b/alvr/client_core/src/c_api.rs @@ -41,6 +41,10 @@ pub struct AlvrClientCapabilities { encoder_high_profile: bool, encoder_10_bits: bool, encoder_av1: bool, + prefer_10bit: bool, + prefer_full_range: bool, + preferred_encoding_gamma: f32, + prefer_hdr: bool, } #[repr(u8)] @@ -57,7 +61,9 @@ pub enum AlvrEvent { view_width: u32, view_height: u32, refresh_rate_hint: f32, + encoding_gamma: f32, enable_foveated_encoding: bool, + enable_hdr: bool, }, StreamingStopped, Haptics { @@ -283,6 +289,10 @@ pub unsafe extern "C" fn alvr_initialize(capabilities: AlvrClientCapabilities) { encoder_high_profile: capabilities.encoder_high_profile, encoder_10_bits: capabilities.encoder_10_bits, encoder_av1: capabilities.encoder_av1, + prefer_10bit: capabilities.prefer_10bit, + prefer_full_range: capabilities.prefer_full_range, + preferred_encoding_gamma: capabilities.preferred_encoding_gamma, + prefer_hdr: capabilities.prefer_hdr, }; *CLIENT_CORE_CONTEXT.lock() = Some(ClientCoreContext::new(capabilities)); } @@ -328,9 +338,11 @@ pub extern "C" fn alvr_poll_event(out_event: *mut AlvrEvent) -> bool { view_width: stream_config.negotiated_config.view_resolution.x, view_height: stream_config.negotiated_config.view_resolution.y, refresh_rate_hint: stream_config.negotiated_config.refresh_rate_hint, + encoding_gamma: stream_config.negotiated_config.encoding_gamma, enable_foveated_encoding: stream_config .negotiated_config .enable_foveated_encoding, + enable_hdr: stream_config.negotiated_config.enable_hdr, } } ClientCoreEvent::StreamingStopped => AlvrEvent::StreamingStopped, diff --git a/alvr/client_core/src/connection.rs b/alvr/client_core/src/connection.rs index db6ee4074a..d75adf2ed2 100644 --- a/alvr/client_core/src/connection.rs +++ b/alvr/client_core/src/connection.rs @@ -179,6 +179,10 @@ fn connection_pipeline( encoder_10_bits: capabilities.encoder_10_bits, encoder_av1: capabilities.encoder_av1, multimodal_protocol: true, + prefer_10bit: capabilities.prefer_10bit, + prefer_full_range: capabilities.prefer_full_range, + preferred_encoding_gamma: capabilities.preferred_encoding_gamma, + prefer_hdr: capabilities.prefer_hdr, }) .to_con()?, ), diff --git a/alvr/client_core/src/lib.rs b/alvr/client_core/src/lib.rs index 9088c525f6..e531087d52 100644 --- a/alvr/client_core/src/lib.rs +++ b/alvr/client_core/src/lib.rs @@ -78,6 +78,10 @@ pub struct ClientCapabilities { pub encoder_high_profile: bool, pub encoder_10_bits: bool, pub encoder_av1: bool, + pub prefer_10bit: bool, + pub prefer_full_range: bool, + pub preferred_encoding_gamma: f32, + pub prefer_hdr: bool, } pub struct ClientCoreContext { diff --git a/alvr/client_mock/src/main.rs b/alvr/client_mock/src/main.rs index af396d4fad..5f30292e04 100644 --- a/alvr/client_mock/src/main.rs +++ b/alvr/client_mock/src/main.rs @@ -210,6 +210,10 @@ fn client_thread( encoder_high_profile: false, encoder_10_bits: false, encoder_av1: false, + prefer_10bit: false, + prefer_full_range: true, + preferred_encoding_gamma: 1.0, + prefer_hdr: false, }; let client_core_context = Arc::new(ClientCoreContext::new(capabilities)); diff --git a/alvr/client_openxr/src/lib.rs b/alvr/client_openxr/src/lib.rs index 9670449952..6c07bfa616 100644 --- a/alvr/client_openxr/src/lib.rs +++ b/alvr/client_openxr/src/lib.rs @@ -244,6 +244,10 @@ pub fn entry_point() { encoder_high_profile: platform != Platform::Unknown, encoder_10_bits: platform != Platform::Unknown, encoder_av1: matches!(platform, Platform::Quest3 | Platform::Quest3S), + prefer_10bit: false, + prefer_full_range: true, + preferred_encoding_gamma: 1.0, + prefer_hdr: false, }; let core_context = Arc::new(ClientCoreContext::new(capabilities)); diff --git a/alvr/client_openxr/src/stream.rs b/alvr/client_openxr/src/stream.rs index f60d961cdb..2710bb6abb 100644 --- a/alvr/client_openxr/src/stream.rs +++ b/alvr/client_openxr/src/stream.rs @@ -34,9 +34,10 @@ const DECODER_MAX_TIMEOUT_MULTIPLIER: f32 = 0.8; pub struct ParsedStreamConfig { pub view_resolution: UVec2, pub refresh_rate_hint: f32, + pub encoding_gamma: f32, + pub enable_hdr: bool, pub foveated_encoding_config: Option, pub clientside_foveation_config: Option, - pub encoder_config: EncoderConfig, pub face_sources_config: Option, pub body_sources_config: Option, pub prefers_multimodal_input: bool, @@ -51,6 +52,8 @@ impl ParsedStreamConfig { ParsedStreamConfig { view_resolution: config.negotiated_config.view_resolution, refresh_rate_hint: config.negotiated_config.refresh_rate_hint, + encoding_gamma: config.negotiated_config.encoding_gamma, + enable_hdr: config.negotiated_config.enable_hdr, foveated_encoding_config: config .negotiated_config .enable_foveated_encoding @@ -62,7 +65,6 @@ impl ParsedStreamConfig { .clientside_foveation .as_option() .cloned(), - encoder_config: config.settings.video.encoder_config.clone(), face_sources_config: config .settings .headset @@ -153,8 +155,7 @@ impl StreamContext { None }; - let format = - graphics::swapchain_format(&gfx_ctx, &xr_ctx.session, config.encoder_config.enable_hdr); + let format = graphics::swapchain_format(&gfx_ctx, &xr_ctx.session, config.enable_hdr); let swapchains = [ graphics::create_swapchain( @@ -192,10 +193,9 @@ impl StreamContext { ], format, config.foveated_encoding_config.clone(), - platform != Platform::Lynx - && !((platform.is_pico()) && config.encoder_config.enable_hdr), - !config.encoder_config.enable_hdr, - config.encoder_config.encoding_gamma, + platform != Platform::Lynx && !((platform.is_pico()) && config.enable_hdr), + !config.enable_hdr, + config.encoding_gamma, ); core_ctx.send_playspace( diff --git a/alvr/packets/src/lib.rs b/alvr/packets/src/lib.rs index 131698af9e..40427f8438 100644 --- a/alvr/packets/src/lib.rs +++ b/alvr/packets/src/lib.rs @@ -40,6 +40,10 @@ pub struct VideoStreamingCapabilities { pub encoder_10_bits: bool, pub encoder_av1: bool, pub multimodal_protocol: bool, + pub prefer_10bit: bool, + pub prefer_full_range: bool, + pub preferred_encoding_gamma: f32, + pub prefer_hdr: bool, } // Nasty workaround to make the packet extensible, pushing the limits of protocol compatibility @@ -95,6 +99,12 @@ pub fn decode_video_streaming_capabilities( encoder_10_bits: caps_json["encoder_10_bits"].as_bool().unwrap_or(true), encoder_av1: caps_json["encoder_av1"].as_bool().unwrap_or(true), multimodal_protocol: caps_json["multimodal_protocol"].as_bool().unwrap_or(false), + prefer_10bit: caps_json["prefer_10bit"].as_bool().unwrap_or(false), + prefer_full_range: caps_json["prefer_full_range"].as_bool().unwrap_or(true), + preferred_encoding_gamma: caps_json["preferred_encoding_gamma"] + .as_f64() + .unwrap_or(1.0) as f32, + prefer_hdr: caps_json["prefer_hdr"].as_bool().unwrap_or(false), }) } @@ -119,6 +129,8 @@ pub struct NegotiatedStreamingConfig { // This is needed to detect when to use SteamVR hand trackers. This does NOT imply if multimodal // input is supported pub use_multimodal_protocol: bool, + pub encoding_gamma: f32, + pub enable_hdr: bool, } #[derive(Serialize, Deserialize)] @@ -160,6 +172,8 @@ pub fn decode_stream_config(packet: &StreamConfigPacket) -> Result .unwrap_or_else(|_| settings.video.foveated_encoding.enabled()); let use_multimodal_protocol = json::from_value(negotiated_json["use_multimodal_protocol"].clone()).unwrap_or(false); + let encoding_gamma = json::from_value(negotiated_json["encoding_gamma"].clone()).unwrap_or(1.0); + let enable_hdr = json::from_value(negotiated_json["enable_hdr"].clone()).unwrap_or(false); Ok(StreamConfig { server_version: session_config.server_version, @@ -170,6 +184,8 @@ pub fn decode_stream_config(packet: &StreamConfigPacket) -> Result game_audio_sample_rate, enable_foveated_encoding, use_multimodal_protocol, + encoding_gamma, + enable_hdr, }, }) } diff --git a/alvr/server_core/src/connection.rs b/alvr/server_core/src/connection.rs index b736bad8f5..2a2919fe45 100644 --- a/alvr/server_core/src/connection.rs +++ b/alvr/server_core/src/connection.rs @@ -562,16 +562,49 @@ fn connection_pipeline( initial_settings.video.encoder_config.h264_profile }; - let enable_10_bits_encoding = if initial_settings.video.encoder_config.use_10bit { - let enable = streaming_caps.encoder_10_bits; + let mut enable_10_bits_encoding = if initial_settings + .video + .encoder_config + .server_overrides_use_10bit + { + initial_settings.video.encoder_config.use_10bit + } else { + streaming_caps.prefer_10bit + }; - if !enable { - warn!("10 bits encoding is not supported by the client."); - } + if enable_10_bits_encoding && !streaming_caps.encoder_10_bits { + warn!("10 bits encoding is not supported by the client."); + enable_10_bits_encoding = false + } - enable + let use_full_range = if initial_settings + .video + .encoder_config + .server_overrides_use_full_range + { + initial_settings.video.encoder_config.use_full_range } else { - false + streaming_caps.prefer_full_range + }; + + let enable_hdr = if initial_settings + .video + .encoder_config + .server_overrides_enable_hdr + { + initial_settings.video.encoder_config.enable_hdr + } else { + streaming_caps.prefer_hdr + }; + + let encoding_gamma = if initial_settings + .video + .encoder_config + .server_overrides_encoding_gamma + { + initial_settings.video.encoder_config.encoding_gamma + } else { + streaming_caps.preferred_encoding_gamma }; let codec = if initial_settings.video.preferred_codec == CodecType::AV1 { @@ -635,6 +668,8 @@ fn connection_pipeline( game_audio_sample_rate, enable_foveated_encoding, use_multimodal_protocol: streaming_caps.multimodal_protocol, + encoding_gamma: encoding_gamma, + enable_hdr: enable_hdr, }, ) .to_con()?; @@ -652,6 +687,9 @@ fn connection_pipeline( new_openvr_config.enable_foveated_encoding = enable_foveated_encoding; new_openvr_config.h264_profile = encoder_profile as _; new_openvr_config.use_10bit_encoder = enable_10_bits_encoding; + new_openvr_config.use_full_range_encoding = use_full_range; + new_openvr_config.enable_hdr = enable_hdr; + new_openvr_config.encoding_gamma = encoding_gamma; new_openvr_config.codec = codec as _; if session_manager_lock.session().openvr_config != new_openvr_config { diff --git a/alvr/session/src/settings.rs b/alvr/session/src/settings.rs index 78088cccca..7b0d9bdbe3 100644 --- a/alvr/session/src/settings.rs +++ b/alvr/session/src/settings.rs @@ -224,19 +224,33 @@ CABAC produces better compression but it's significantly slower and may lead to pub entropy_coding: EntropyCoding, #[schema(strings( - display_name = "10 bit encoding", - help = "Sets the encoder to use 10 bits per channel instead of 8. Does not work on Linux with Nvidia" + display_name = "10-bit encoding", + help = "Sets the encoder to use 10 bits per channel instead of 8, if the client has no preference. Does not work on Linux with Nvidia" ))] #[schema(flag = "steamvr-restart")] pub use_10bit: bool, + #[schema(strings( + display_name = "Override headset's preference for 10-bit encoding", + help = "Override the headset client's preference for 10-bit encoding." + ))] + #[schema(flag = "steamvr-restart")] + pub server_overrides_use_10bit: bool, + #[schema(strings( display_name = "Full range color", - help = "Sets the encoder to encode full range RGB (0-255) instead of limited/video range RGB (16-235)" + help = "Sets the encoder to encode full range RGB (0-255) instead of limited/video range RGB (16-235), if the client has no preference" ))] #[schema(flag = "steamvr-restart")] pub use_full_range: bool, + #[schema(strings( + display_name = "Override headset's preference for full range color", + help = "The server will override the headset client's preference for full range color." + ))] + #[schema(flag = "steamvr-restart")] + pub server_overrides_use_full_range: bool, + #[schema(strings( display_name = "Encoding Gamma", help = "To prioritize darker pixels at the expense of potentially additional banding in midtones, set to 2.2. To allow the encoder to decide priority on its own, set to 1.0." @@ -244,13 +258,27 @@ CABAC produces better compression but it's significantly slower and may lead to #[schema(flag = "steamvr-restart")] pub encoding_gamma: f32, + #[schema(strings( + display_name = "Override headset's preference for encoding gamma", + help = "The server will override the headset client's preference for encoding gamma." + ))] + #[schema(flag = "steamvr-restart")] + pub server_overrides_encoding_gamma: bool, + #[schema(strings( display_name = "Enable HDR", - help = "Composite VR layers to an RGBA float16 framebuffer, and do sRGB/YUV conversions in shader code." + help = "If the client has no preference, enables compositing VR layers to an RGBA float16 framebuffer, and doing sRGB/YUV conversions in shader code." ))] #[schema(flag = "steamvr-restart")] pub enable_hdr: bool, + #[schema(strings( + display_name = "Override headset's preference for HDR", + help = "The server will override the headset client's preference for HDR." + ))] + #[schema(flag = "steamvr-restart")] + pub server_overrides_enable_hdr: bool, + #[schema(strings( display_name = "Force HDR sRGB Correction", help = "Forces sRGB correction on all composited SteamVR layers. Useful if an HDR-injected game is too dark." @@ -1332,9 +1360,13 @@ pub fn session_settings_default() -> SettingsDefault { variant: EntropyCodingDefaultVariant::Cavlc, }, use_10bit: false, + server_overrides_use_10bit: false, use_full_range: true, + server_overrides_use_full_range: false, encoding_gamma: 1.0, + server_overrides_encoding_gamma: false, enable_hdr: false, + server_overrides_enable_hdr: false, force_hdr_srgb_correction: false, clamp_hdr_extended_range: false, nvenc: NvencConfigDefault { @@ -1443,7 +1475,7 @@ pub fn session_settings_default() -> SettingsDefault { }, force_software_decoder: false, color_correction: SwitchDefault { - enabled: true, + enabled: false, content: ColorCorrectionConfigDefault { brightness: 0., contrast: 0.,