diff options
Diffstat (limited to 'components/shared/webxr/session.rs')
-rw-r--r-- | components/shared/webxr/session.rs | 531 |
1 files changed, 531 insertions, 0 deletions
diff --git a/components/shared/webxr/session.rs b/components/shared/webxr/session.rs new file mode 100644 index 00000000000..be731b8c243 --- /dev/null +++ b/components/shared/webxr/session.rs @@ -0,0 +1,531 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::channel; +use crate::ContextId; +use crate::DeviceAPI; +use crate::Error; +use crate::Event; +use crate::Floor; +use crate::Frame; +use crate::FrameUpdateEvent; +use crate::HitTestId; +use crate::HitTestSource; +use crate::InputSource; +use crate::LayerGrandManager; +use crate::LayerId; +use crate::LayerInit; +use crate::Native; +use crate::Receiver; +use crate::Sender; +use crate::Viewport; +use crate::Viewports; + +use euclid::Point2D; +use euclid::Rect; +use euclid::RigidTransform3D; +use euclid::Size2D; + +use log::warn; + +use std::thread; +use std::time::Duration; + +#[cfg(feature = "ipc")] +use serde::{Deserialize, Serialize}; + +// How long to wait for an rAF. +static TIMEOUT: Duration = Duration::from_millis(5); + +/// https://www.w3.org/TR/webxr/#xrsessionmode-enum +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))] +pub enum SessionMode { + Inline, + ImmersiveVR, + ImmersiveAR, +} + +/// https://immersive-web.github.io/webxr/#dictdef-xrsessioninit +#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))] +pub struct SessionInit { + pub required_features: Vec<String>, + pub optional_features: Vec<String>, + /// Secondary views are enabled with the `secondary-view` feature + /// but for performance reasons we also ask users to enable this pref + /// for now. + pub first_person_observer_view: bool, +} + +impl SessionInit { + /// Helper function for validating a list of requested features against + /// a list of supported features for a given mode + pub fn validate(&self, mode: SessionMode, supported: &[String]) -> Result<Vec<String>, Error> { + for f in &self.required_features { + // viewer and local in immersive are granted by default + // https://immersive-web.github.io/webxr/#default-features + if f == "viewer" || (f == "local" && mode != SessionMode::Inline) { + continue; + } + + if !supported.contains(f) { + return Err(Error::UnsupportedFeature(f.into())); + } + } + let mut granted = self.required_features.clone(); + for f in &self.optional_features { + if f == "viewer" + || (f == "local" && mode != SessionMode::Inline) + || supported.contains(f) + { + granted.push(f.clone()); + } + } + + Ok(granted) + } + + pub fn feature_requested(&self, f: &str) -> bool { + self.required_features + .iter() + .chain(self.optional_features.iter()) + .find(|x| *x == f) + .is_some() + } +} + +/// https://immersive-web.github.io/webxr-ar-module/#xrenvironmentblendmode-enum +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))] +pub enum EnvironmentBlendMode { + Opaque, + AlphaBlend, + Additive, +} + +// The messages that are sent from the content thread to the session thread. +#[derive(Debug)] +#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))] +enum SessionMsg { + CreateLayer(ContextId, LayerInit, Sender<Result<LayerId, Error>>), + DestroyLayer(ContextId, LayerId), + SetLayers(Vec<(ContextId, LayerId)>), + SetEventDest(Sender<Event>), + UpdateClipPlanes(/* near */ f32, /* far */ f32), + StartRenderLoop, + RenderAnimationFrame, + RequestHitTest(HitTestSource), + CancelHitTest(HitTestId), + UpdateFrameRate(f32, Sender<f32>), + Quit, + GetBoundsGeometry(Sender<Option<Vec<Point2D<f32, Floor>>>>), +} + +#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))] +#[derive(Clone)] +pub struct Quitter { + sender: Sender<SessionMsg>, +} + +impl Quitter { + pub fn quit(&self) { + let _ = self.sender.send(SessionMsg::Quit); + } +} + +/// An object that represents an XR session. +/// This is owned by the content thread. +/// https://www.w3.org/TR/webxr/#xrsession-interface +#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))] +pub struct Session { + floor_transform: Option<RigidTransform3D<f32, Native, Floor>>, + viewports: Viewports, + sender: Sender<SessionMsg>, + environment_blend_mode: EnvironmentBlendMode, + initial_inputs: Vec<InputSource>, + granted_features: Vec<String>, + id: SessionId, + supported_frame_rates: Vec<f32>, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[cfg_attr(feature = "ipc", derive(Deserialize, Serialize))] +pub struct SessionId(pub(crate) u32); + +impl Session { + pub fn id(&self) -> SessionId { + self.id + } + + pub fn floor_transform(&self) -> Option<RigidTransform3D<f32, Native, Floor>> { + self.floor_transform.clone() + } + + pub fn reference_space_bounds(&self) -> Option<Vec<Point2D<f32, Floor>>> { + let (sender, receiver) = channel().ok()?; + let _ = self.sender.send(SessionMsg::GetBoundsGeometry(sender)); + receiver.recv().ok()? + } + + pub fn initial_inputs(&self) -> &[InputSource] { + &self.initial_inputs + } + + pub fn environment_blend_mode(&self) -> EnvironmentBlendMode { + self.environment_blend_mode + } + + pub fn viewports(&self) -> &[Rect<i32, Viewport>] { + &self.viewports.viewports + } + + /// A resolution large enough to contain all the viewports. + /// https://immersive-web.github.io/webxr/#recommended-webgl-framebuffer-resolution + /// + /// Returns None if the session is inline + pub fn recommended_framebuffer_resolution(&self) -> Option<Size2D<i32, Viewport>> { + self.viewports() + .iter() + .fold(None::<Rect<_, _>>, |acc, vp| { + Some(acc.map(|a| a.union(vp)).unwrap_or(*vp)) + }) + .map(|rect| Size2D::new(rect.max_x(), rect.max_y())) + } + + pub fn create_layer(&self, context_id: ContextId, init: LayerInit) -> Result<LayerId, Error> { + let (sender, receiver) = channel().map_err(|_| Error::CommunicationError)?; + let _ = self + .sender + .send(SessionMsg::CreateLayer(context_id, init, sender)); + receiver.recv().map_err(|_| Error::CommunicationError)? + } + + /// Destroy a layer + pub fn destroy_layer(&self, context_id: ContextId, layer_id: LayerId) { + let _ = self + .sender + .send(SessionMsg::DestroyLayer(context_id, layer_id)); + } + + pub fn set_layers(&self, layers: Vec<(ContextId, LayerId)>) { + let _ = self.sender.send(SessionMsg::SetLayers(layers)); + } + + pub fn start_render_loop(&mut self) { + let _ = self.sender.send(SessionMsg::StartRenderLoop); + } + + pub fn update_clip_planes(&mut self, near: f32, far: f32) { + let _ = self.sender.send(SessionMsg::UpdateClipPlanes(near, far)); + } + + pub fn set_event_dest(&mut self, dest: Sender<Event>) { + let _ = self.sender.send(SessionMsg::SetEventDest(dest)); + } + + pub fn render_animation_frame(&mut self) { + let _ = self.sender.send(SessionMsg::RenderAnimationFrame); + } + + pub fn end_session(&mut self) { + let _ = self.sender.send(SessionMsg::Quit); + } + + pub fn apply_event(&mut self, event: FrameUpdateEvent) { + match event { + FrameUpdateEvent::UpdateFloorTransform(floor) => self.floor_transform = floor, + FrameUpdateEvent::UpdateViewports(vp) => self.viewports = vp, + FrameUpdateEvent::HitTestSourceAdded(_) => (), + } + } + + pub fn granted_features(&self) -> &[String] { + &self.granted_features + } + + pub fn request_hit_test(&self, source: HitTestSource) { + let _ = self.sender.send(SessionMsg::RequestHitTest(source)); + } + + pub fn cancel_hit_test(&self, id: HitTestId) { + let _ = self.sender.send(SessionMsg::CancelHitTest(id)); + } + + pub fn update_frame_rate(&mut self, rate: f32, sender: Sender<f32>) { + let _ = self.sender.send(SessionMsg::UpdateFrameRate(rate, sender)); + } + + pub fn supported_frame_rates(&self) -> &[f32] { + &self.supported_frame_rates + } +} + +#[derive(PartialEq)] +enum RenderState { + NotInRenderLoop, + InRenderLoop, + PendingQuit, +} + +/// For devices that want to do their own thread management, the `SessionThread` type is exposed. +pub struct SessionThread<Device> { + receiver: Receiver<SessionMsg>, + sender: Sender<SessionMsg>, + layers: Vec<(ContextId, LayerId)>, + pending_layers: Option<Vec<(ContextId, LayerId)>>, + frame_count: u64, + frame_sender: Sender<Frame>, + running: bool, + device: Device, + id: SessionId, + render_state: RenderState, +} + +impl<Device> SessionThread<Device> +where + Device: DeviceAPI, +{ + pub fn new( + mut device: Device, + frame_sender: Sender<Frame>, + id: SessionId, + ) -> Result<Self, Error> { + let (sender, receiver) = crate::channel().or(Err(Error::CommunicationError))?; + device.set_quitter(Quitter { + sender: sender.clone(), + }); + let frame_count = 0; + let running = true; + let layers = Vec::new(); + let pending_layers = None; + Ok(SessionThread { + sender, + receiver, + device, + layers, + pending_layers, + frame_count, + frame_sender, + running, + id, + render_state: RenderState::NotInRenderLoop, + }) + } + + pub fn new_session(&mut self) -> Session { + let floor_transform = self.device.floor_transform(); + let viewports = self.device.viewports(); + let sender = self.sender.clone(); + let initial_inputs = self.device.initial_inputs(); + let environment_blend_mode = self.device.environment_blend_mode(); + let granted_features = self.device.granted_features().into(); + let supported_frame_rates = self.device.supported_frame_rates(); + Session { + floor_transform, + viewports, + sender, + initial_inputs, + environment_blend_mode, + granted_features, + id: self.id, + supported_frame_rates, + } + } + + pub fn run(&mut self) { + loop { + if let Ok(msg) = self.receiver.recv() { + if !self.handle_msg(msg) { + self.running = false; + break; + } + } else { + break; + } + } + } + + fn handle_msg(&mut self, msg: SessionMsg) -> bool { + log::debug!("processing {:?}", msg); + match msg { + SessionMsg::SetEventDest(dest) => { + self.device.set_event_dest(dest); + } + SessionMsg::RequestHitTest(source) => { + self.device.request_hit_test(source); + } + SessionMsg::CancelHitTest(id) => { + self.device.cancel_hit_test(id); + } + SessionMsg::CreateLayer(context_id, layer_init, sender) => { + let result = self.device.create_layer(context_id, layer_init); + let _ = sender.send(result); + } + SessionMsg::DestroyLayer(context_id, layer_id) => { + self.layers.retain(|&(_, other_id)| layer_id != other_id); + self.device.destroy_layer(context_id, layer_id); + } + SessionMsg::SetLayers(layers) => { + self.pending_layers = Some(layers); + } + SessionMsg::StartRenderLoop => { + if let Some(layers) = self.pending_layers.take() { + self.layers = layers; + } + let frame = match self.device.begin_animation_frame(&self.layers[..]) { + Some(frame) => frame, + None => { + warn!("Device stopped providing frames, exiting"); + return false; + } + }; + self.render_state = RenderState::InRenderLoop; + let _ = self.frame_sender.send(frame); + } + SessionMsg::UpdateClipPlanes(near, far) => self.device.update_clip_planes(near, far), + SessionMsg::RenderAnimationFrame => { + self.frame_count += 1; + + self.device.end_animation_frame(&self.layers[..]); + + if self.render_state == RenderState::PendingQuit { + self.quit(); + return false; + } + + if let Some(layers) = self.pending_layers.take() { + self.layers = layers; + } + #[allow(unused_mut)] + let mut frame = match self.device.begin_animation_frame(&self.layers[..]) { + Some(frame) => frame, + None => { + warn!("Device stopped providing frames, exiting"); + return false; + } + }; + + let _ = self.frame_sender.send(frame); + } + SessionMsg::UpdateFrameRate(rate, sender) => { + let new_framerate = self.device.update_frame_rate(rate); + let _ = sender.send(new_framerate); + } + SessionMsg::Quit => { + if self.render_state == RenderState::NotInRenderLoop { + self.quit(); + return false; + } else { + self.render_state = RenderState::PendingQuit; + } + } + SessionMsg::GetBoundsGeometry(sender) => { + let bounds = self.device.reference_space_bounds(); + let _ = sender.send(bounds); + } + } + true + } + + fn quit(&mut self) { + self.render_state = RenderState::NotInRenderLoop; + self.device.quit(); + } +} + +/// Devices that need to can run sessions on the main thread. +pub trait MainThreadSession: 'static { + fn run_one_frame(&mut self); + fn running(&self) -> bool; +} + +impl<Device> MainThreadSession for SessionThread<Device> +where + Device: DeviceAPI, +{ + fn run_one_frame(&mut self) { + let frame_count = self.frame_count; + while frame_count == self.frame_count && self.running { + if let Ok(msg) = crate::recv_timeout(&self.receiver, TIMEOUT) { + self.running = self.handle_msg(msg); + } else { + break; + } + } + } + + fn running(&self) -> bool { + self.running + } +} + +/// A type for building XR sessions +pub struct SessionBuilder<'a, GL> { + sessions: &'a mut Vec<Box<dyn MainThreadSession>>, + frame_sender: Sender<Frame>, + layer_grand_manager: LayerGrandManager<GL>, + id: SessionId, +} + +impl<'a, GL: 'static> SessionBuilder<'a, GL> { + pub fn id(&self) -> SessionId { + self.id + } + + pub(crate) fn new( + sessions: &'a mut Vec<Box<dyn MainThreadSession>>, + frame_sender: Sender<Frame>, + layer_grand_manager: LayerGrandManager<GL>, + id: SessionId, + ) -> Self { + SessionBuilder { + sessions, + frame_sender, + layer_grand_manager, + id, + } + } + + /// For devices which are happy to hand over thread management to webxr. + pub fn spawn<Device, Factory>(self, factory: Factory) -> Result<Session, Error> + where + Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error> + Send, + Device: DeviceAPI, + { + let (acks, ackr) = crate::channel().or(Err(Error::CommunicationError))?; + let frame_sender = self.frame_sender; + let layer_grand_manager = self.layer_grand_manager; + let id = self.id; + thread::spawn(move || { + match factory(layer_grand_manager) + .and_then(|device| SessionThread::new(device, frame_sender, id)) + { + Ok(mut thread) => { + let session = thread.new_session(); + let _ = acks.send(Ok(session)); + thread.run(); + } + Err(err) => { + let _ = acks.send(Err(err)); + } + } + }); + ackr.recv().unwrap_or(Err(Error::CommunicationError)) + } + + /// For devices that need to run on the main thread. + pub fn run_on_main_thread<Device, Factory>(self, factory: Factory) -> Result<Session, Error> + where + Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error>, + Device: DeviceAPI, + { + let device = factory(self.layer_grand_manager)?; + let frame_sender = self.frame_sender; + let mut session_thread = SessionThread::new(device, frame_sender, self.id)?; + let session = session_thread.new_session(); + self.sessions.push(Box::new(session_thread)); + Ok(session) + } +} |