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