webxr_api/
session.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::thread;
6use std::time::Duration;
7
8use euclid::{Point2D, Rect, RigidTransform3D, Size2D};
9use log::warn;
10#[cfg(feature = "ipc")]
11use serde::{Deserialize, Serialize};
12
13use crate::{
14    ContextId, DeviceAPI, Error, Event, Floor, Frame, FrameUpdateEvent, HitTestId, HitTestSource,
15    InputSource, LayerGrandManager, LayerId, LayerInit, Native, Viewport, Viewports, WebXrReceiver,
16    WebXrSender, webxr_channel,
17};
18
19// How long to wait for an rAF.
20static TIMEOUT: Duration = Duration::from_millis(5);
21
22/// <https://www.w3.org/TR/webxr/#xrsessionmode-enum>
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
25pub enum SessionMode {
26    Inline,
27    ImmersiveVR,
28    ImmersiveAR,
29}
30
31/// <https://immersive-web.github.io/webxr/#dictdef-xrsessioninit>
32#[derive(Clone, Debug, Eq, PartialEq)]
33#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
34pub struct SessionInit {
35    pub required_features: Vec<String>,
36    pub optional_features: Vec<String>,
37    /// Secondary views are enabled with the `secondary-view` feature
38    /// but for performance reasons we also ask users to enable this pref
39    /// for now.
40    pub first_person_observer_view: bool,
41}
42
43impl SessionInit {
44    /// Helper function for validating a list of requested features against
45    /// a list of supported features for a given mode
46    pub fn validate(&self, mode: SessionMode, supported: &[String]) -> Result<Vec<String>, Error> {
47        for f in &self.required_features {
48            // viewer and local in immersive are granted by default
49            // https://immersive-web.github.io/webxr/#default-features
50            if f == "viewer" || (f == "local" && mode != SessionMode::Inline) {
51                continue;
52            }
53
54            if !supported.contains(f) {
55                return Err(Error::UnsupportedFeature(f.into()));
56            }
57        }
58        let mut granted = self.required_features.clone();
59        for f in &self.optional_features {
60            if f == "viewer" ||
61                (f == "local" && mode != SessionMode::Inline) ||
62                supported.contains(f)
63            {
64                granted.push(f.clone());
65            }
66        }
67
68        Ok(granted)
69    }
70
71    pub fn feature_requested(&self, f: &str) -> bool {
72        self.required_features
73            .iter()
74            .chain(self.optional_features.iter())
75            .any(|x| *x == f)
76    }
77}
78
79/// <https://immersive-web.github.io/webxr-ar-module/#xrenvironmentblendmode-enum>
80#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
82pub enum EnvironmentBlendMode {
83    Opaque,
84    AlphaBlend,
85    Additive,
86}
87
88// The messages that are sent from the content thread to the session thread.
89#[derive(Debug)]
90#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
91enum SessionMsg {
92    CreateLayer(ContextId, LayerInit, WebXrSender<Result<LayerId, Error>>),
93    DestroyLayer(ContextId, LayerId),
94    SetLayers(Vec<(ContextId, LayerId)>),
95    SetEventDest(WebXrSender<Event>),
96    UpdateClipPlanes(/* near */ f32, /* far */ f32),
97    StartRenderLoop,
98    RenderAnimationFrame,
99    RequestHitTest(HitTestSource),
100    CancelHitTest(HitTestId),
101    UpdateFrameRate(f32, WebXrSender<f32>),
102    Quit,
103    GetBoundsGeometry(WebXrSender<Option<Vec<Point2D<f32, Floor>>>>),
104}
105
106#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
107#[derive(Clone)]
108pub struct Quitter {
109    sender: WebXrSender<SessionMsg>,
110}
111
112impl Quitter {
113    pub fn quit(&self) {
114        let _ = self.sender.send(SessionMsg::Quit);
115    }
116}
117
118/// An object that represents an XR session.
119/// This is owned by the content thread.
120/// <https://www.w3.org/TR/webxr/#xrsession-interface>
121#[cfg_attr(feature = "ipc", derive(Serialize, Deserialize))]
122pub struct Session {
123    floor_transform: Option<RigidTransform3D<f32, Native, Floor>>,
124    viewports: Viewports,
125    sender: WebXrSender<SessionMsg>,
126    environment_blend_mode: EnvironmentBlendMode,
127    initial_inputs: Vec<InputSource>,
128    granted_features: Vec<String>,
129    id: SessionId,
130    supported_frame_rates: Vec<f32>,
131}
132
133#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
134#[cfg_attr(feature = "ipc", derive(Deserialize, Serialize))]
135pub struct SessionId(pub(crate) u32);
136
137impl Session {
138    pub fn id(&self) -> SessionId {
139        self.id
140    }
141
142    pub fn floor_transform(&self) -> Option<RigidTransform3D<f32, Native, Floor>> {
143        self.floor_transform
144    }
145
146    pub fn reference_space_bounds(&self) -> Option<Vec<Point2D<f32, Floor>>> {
147        let (sender, receiver) = webxr_channel().ok()?;
148        let _ = self.sender.send(SessionMsg::GetBoundsGeometry(sender));
149        receiver.recv().ok()?
150    }
151
152    pub fn initial_inputs(&self) -> &[InputSource] {
153        &self.initial_inputs
154    }
155
156    pub fn environment_blend_mode(&self) -> EnvironmentBlendMode {
157        self.environment_blend_mode
158    }
159
160    pub fn viewports(&self) -> &[Rect<i32, Viewport>] {
161        &self.viewports.viewports
162    }
163
164    /// A resolution large enough to contain all the viewports.
165    /// <https://immersive-web.github.io/webxr/#recommended-webgl-framebuffer-resolution>
166    ///
167    /// Returns None if the session is inline
168    pub fn recommended_framebuffer_resolution(&self) -> Option<Size2D<i32, Viewport>> {
169        self.viewports()
170            .iter()
171            .fold(None::<Rect<_, _>>, |acc, vp| {
172                Some(acc.map(|a| a.union(vp)).unwrap_or(*vp))
173            })
174            .map(|rect| Size2D::new(rect.max_x(), rect.max_y()))
175    }
176
177    pub fn create_layer(&self, context_id: ContextId, init: LayerInit) -> Result<LayerId, Error> {
178        let (sender, receiver) = webxr_channel().map_err(|_| Error::CommunicationError)?;
179        let _ = self
180            .sender
181            .send(SessionMsg::CreateLayer(context_id, init, sender));
182        receiver.recv().map_err(|_| Error::CommunicationError)?
183    }
184
185    /// Destroy a layer
186    pub fn destroy_layer(&self, context_id: ContextId, layer_id: LayerId) {
187        let _ = self
188            .sender
189            .send(SessionMsg::DestroyLayer(context_id, layer_id));
190    }
191
192    pub fn set_layers(&self, layers: Vec<(ContextId, LayerId)>) {
193        let _ = self.sender.send(SessionMsg::SetLayers(layers));
194    }
195
196    pub fn start_render_loop(&mut self) {
197        let _ = self.sender.send(SessionMsg::StartRenderLoop);
198    }
199
200    pub fn update_clip_planes(&mut self, near: f32, far: f32) {
201        let _ = self.sender.send(SessionMsg::UpdateClipPlanes(near, far));
202    }
203
204    pub fn set_event_dest(&mut self, dest: WebXrSender<Event>) {
205        let _ = self.sender.send(SessionMsg::SetEventDest(dest));
206    }
207
208    pub fn render_animation_frame(&mut self) {
209        let _ = self.sender.send(SessionMsg::RenderAnimationFrame);
210    }
211
212    pub fn end_session(&mut self) {
213        let _ = self.sender.send(SessionMsg::Quit);
214    }
215
216    pub fn apply_event(&mut self, event: FrameUpdateEvent) {
217        match event {
218            FrameUpdateEvent::UpdateFloorTransform(floor) => self.floor_transform = floor,
219            FrameUpdateEvent::UpdateViewports(vp) => self.viewports = vp,
220            FrameUpdateEvent::HitTestSourceAdded(_) => (),
221        }
222    }
223
224    pub fn granted_features(&self) -> &[String] {
225        &self.granted_features
226    }
227
228    pub fn request_hit_test(&self, source: HitTestSource) {
229        let _ = self.sender.send(SessionMsg::RequestHitTest(source));
230    }
231
232    pub fn cancel_hit_test(&self, id: HitTestId) {
233        let _ = self.sender.send(SessionMsg::CancelHitTest(id));
234    }
235
236    pub fn update_frame_rate(&mut self, rate: f32, sender: WebXrSender<f32>) {
237        let _ = self.sender.send(SessionMsg::UpdateFrameRate(rate, sender));
238    }
239
240    pub fn supported_frame_rates(&self) -> &[f32] {
241        &self.supported_frame_rates
242    }
243}
244
245#[derive(PartialEq)]
246enum RenderState {
247    NotInRenderLoop,
248    InRenderLoop,
249    PendingQuit,
250}
251
252/// For devices that want to do their own thread management, the `SessionThread` type is exposed.
253pub struct SessionThread<Device> {
254    receiver: WebXrReceiver<SessionMsg>,
255    sender: WebXrSender<SessionMsg>,
256    layers: Vec<(ContextId, LayerId)>,
257    pending_layers: Option<Vec<(ContextId, LayerId)>>,
258    frame_count: u64,
259    frame_sender: WebXrSender<Frame>,
260    running: bool,
261    device: Device,
262    id: SessionId,
263    render_state: RenderState,
264}
265
266impl<Device> SessionThread<Device>
267where
268    Device: DeviceAPI,
269{
270    pub fn new(
271        mut device: Device,
272        frame_sender: WebXrSender<Frame>,
273        id: SessionId,
274    ) -> Result<Self, Error> {
275        let (sender, receiver) = crate::webxr_channel().or(Err(Error::CommunicationError))?;
276        device.set_quitter(Quitter {
277            sender: sender.clone(),
278        });
279        let frame_count = 0;
280        let running = true;
281        let layers = Vec::new();
282        let pending_layers = None;
283        Ok(SessionThread {
284            sender,
285            receiver,
286            device,
287            layers,
288            pending_layers,
289            frame_count,
290            frame_sender,
291            running,
292            id,
293            render_state: RenderState::NotInRenderLoop,
294        })
295    }
296
297    pub fn new_session(&mut self) -> Session {
298        let floor_transform = self.device.floor_transform();
299        let viewports = self.device.viewports();
300        let sender = self.sender.clone();
301        let initial_inputs = self.device.initial_inputs();
302        let environment_blend_mode = self.device.environment_blend_mode();
303        let granted_features = self.device.granted_features().into();
304        let supported_frame_rates = self.device.supported_frame_rates();
305        Session {
306            floor_transform,
307            viewports,
308            sender,
309            initial_inputs,
310            environment_blend_mode,
311            granted_features,
312            id: self.id,
313            supported_frame_rates,
314        }
315    }
316
317    pub fn run(&mut self) {
318        while let Ok(msg) = self.receiver.recv() {
319            if !self.handle_msg(msg) {
320                self.running = false;
321                break;
322            }
323        }
324    }
325
326    fn handle_msg(&mut self, msg: SessionMsg) -> bool {
327        log::debug!("processing {:?}", msg);
328        match msg {
329            SessionMsg::SetEventDest(dest) => {
330                self.device.set_event_dest(dest);
331            },
332            SessionMsg::RequestHitTest(source) => {
333                self.device.request_hit_test(source);
334            },
335            SessionMsg::CancelHitTest(id) => {
336                self.device.cancel_hit_test(id);
337            },
338            SessionMsg::CreateLayer(context_id, layer_init, sender) => {
339                let result = self.device.create_layer(context_id, layer_init);
340                let _ = sender.send(result);
341            },
342            SessionMsg::DestroyLayer(context_id, layer_id) => {
343                self.layers.retain(|&(_, other_id)| layer_id != other_id);
344                self.device.destroy_layer(context_id, layer_id);
345            },
346            SessionMsg::SetLayers(layers) => {
347                self.pending_layers = Some(layers);
348            },
349            SessionMsg::StartRenderLoop => {
350                if let Some(layers) = self.pending_layers.take() {
351                    self.layers = layers;
352                }
353                let frame = match self.device.begin_animation_frame(&self.layers[..]) {
354                    Some(frame) => frame,
355                    None => {
356                        warn!("Device stopped providing frames, exiting");
357                        return false;
358                    },
359                };
360                self.render_state = RenderState::InRenderLoop;
361                let _ = self.frame_sender.send(frame);
362            },
363            SessionMsg::UpdateClipPlanes(near, far) => self.device.update_clip_planes(near, far),
364            SessionMsg::RenderAnimationFrame => {
365                self.frame_count += 1;
366
367                self.device.end_animation_frame(&self.layers[..]);
368
369                if self.render_state == RenderState::PendingQuit {
370                    self.quit();
371                    return false;
372                }
373
374                if let Some(layers) = self.pending_layers.take() {
375                    self.layers = layers;
376                }
377                #[allow(unused_mut)]
378                let mut frame = match self.device.begin_animation_frame(&self.layers[..]) {
379                    Some(frame) => frame,
380                    None => {
381                        warn!("Device stopped providing frames, exiting");
382                        return false;
383                    },
384                };
385
386                let _ = self.frame_sender.send(frame);
387            },
388            SessionMsg::UpdateFrameRate(rate, sender) => {
389                let new_framerate = self.device.update_frame_rate(rate);
390                let _ = sender.send(new_framerate);
391            },
392            SessionMsg::Quit => {
393                if self.render_state == RenderState::NotInRenderLoop {
394                    self.quit();
395                    return false;
396                } else {
397                    self.render_state = RenderState::PendingQuit;
398                }
399            },
400            SessionMsg::GetBoundsGeometry(sender) => {
401                let bounds = self.device.reference_space_bounds();
402                let _ = sender.send(bounds);
403            },
404        }
405        true
406    }
407
408    fn quit(&mut self) {
409        self.render_state = RenderState::NotInRenderLoop;
410        self.device.quit();
411    }
412}
413
414/// Devices that need to can run sessions on the main thread.
415pub trait MainThreadSession: 'static {
416    fn run_one_frame(&mut self);
417    fn running(&self) -> bool;
418}
419
420impl<Device> MainThreadSession for SessionThread<Device>
421where
422    Device: DeviceAPI,
423{
424    fn run_one_frame(&mut self) {
425        let frame_count = self.frame_count;
426        while frame_count == self.frame_count && self.running {
427            if let Ok(msg) = crate::recv_timeout(&self.receiver, TIMEOUT) {
428                self.running = self.handle_msg(msg);
429            } else {
430                break;
431            }
432        }
433    }
434
435    fn running(&self) -> bool {
436        self.running
437    }
438}
439
440/// A type for building XR sessions
441pub struct SessionBuilder<'a, GL> {
442    sessions: &'a mut Vec<Box<dyn MainThreadSession>>,
443    frame_sender: WebXrSender<Frame>,
444    layer_grand_manager: LayerGrandManager<GL>,
445    id: SessionId,
446}
447
448impl<'a, GL: 'static> SessionBuilder<'a, GL> {
449    pub fn id(&self) -> SessionId {
450        self.id
451    }
452
453    pub(crate) fn new(
454        sessions: &'a mut Vec<Box<dyn MainThreadSession>>,
455        frame_sender: WebXrSender<Frame>,
456        layer_grand_manager: LayerGrandManager<GL>,
457        id: SessionId,
458    ) -> Self {
459        SessionBuilder {
460            sessions,
461            frame_sender,
462            layer_grand_manager,
463            id,
464        }
465    }
466
467    /// For devices which are happy to hand over thread management to webxr.
468    pub fn spawn<Device, Factory>(self, factory: Factory) -> Result<Session, Error>
469    where
470        Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error> + Send,
471        Device: DeviceAPI,
472    {
473        let (acks, ackr) = crate::webxr_channel().or(Err(Error::CommunicationError))?;
474        let frame_sender = self.frame_sender;
475        let layer_grand_manager = self.layer_grand_manager;
476        let id = self.id;
477        thread::spawn(move || {
478            match factory(layer_grand_manager)
479                .and_then(|device| SessionThread::new(device, frame_sender, id))
480            {
481                Ok(mut thread) => {
482                    let session = thread.new_session();
483                    let _ = acks.send(Ok(session));
484                    thread.run();
485                },
486                Err(err) => {
487                    let _ = acks.send(Err(err));
488                },
489            }
490        });
491        ackr.recv().unwrap_or(Err(Error::CommunicationError))
492    }
493
494    /// For devices that need to run on the main thread.
495    pub fn run_on_main_thread<Device, Factory>(self, factory: Factory) -> Result<Session, Error>
496    where
497        Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error>,
498        Device: DeviceAPI,
499    {
500        let device = factory(self.layer_grand_manager)?;
501        let frame_sender = self.frame_sender;
502        let mut session_thread = SessionThread::new(device, frame_sender, self.id)?;
503        let session = session_thread.new_session();
504        self.sessions.push(Box::new(session_thread));
505        Ok(session)
506    }
507}