Skip to main content

servo_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;
10use malloc_size_of_derive::MallocSizeOf;
11use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
12use serde::{Deserialize, Serialize};
13use servo_base::generic_channel::{self, GenericReceiver, GenericSender};
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, MallocSizeOf)]
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    SetFrameDest(ProfileGenericCallback<Frame>),
94    UpdateClipPlanes(/* near */ f32, /* far */ f32),
95    StartRenderLoop,
96    RenderAnimationFrame,
97    RequestHitTest(HitTestSource),
98    CancelHitTest(HitTestId),
99    UpdateFrameRate(f32, ProfileGenericCallback<f32>),
100    Quit,
101    GetBoundsGeometry(GenericSender<Option<Vec<Point2D<f32, Floor>>>>),
102}
103
104#[derive(Serialize, Deserialize, Clone)]
105pub struct Quitter {
106    sender: GenericSender<SessionMsg>,
107}
108
109impl Quitter {
110    pub fn quit(&self) {
111        let _ = self.sender.send(SessionMsg::Quit);
112    }
113}
114
115/// An object that represents an XR session.
116/// This is owned by the content thread.
117/// <https://www.w3.org/TR/webxr/#xrsession-interface>
118#[derive(Serialize, Deserialize, MallocSizeOf)]
119pub struct Session {
120    floor_transform: Option<RigidTransform3D<f32, Native, Floor>>,
121    viewports: Viewports,
122    sender: GenericSender<SessionMsg>,
123    environment_blend_mode: EnvironmentBlendMode,
124    initial_inputs: Vec<InputSource>,
125    granted_features: Vec<String>,
126    id: SessionId,
127    supported_frame_rates: Vec<f32>,
128}
129
130#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Deserialize, Serialize, MallocSizeOf)]
131pub struct SessionId(pub(crate) u32);
132
133impl Session {
134    pub fn id(&self) -> SessionId {
135        self.id
136    }
137
138    pub fn floor_transform(&self) -> Option<RigidTransform3D<f32, Native, Floor>> {
139        self.floor_transform
140    }
141
142    pub fn reference_space_bounds(&self) -> Option<Vec<Point2D<f32, Floor>>> {
143        let (sender, receiver) = generic_channel::channel()?;
144        let _ = self.sender.send(SessionMsg::GetBoundsGeometry(sender));
145        receiver.recv().ok()?
146    }
147
148    pub fn initial_inputs(&self) -> &[InputSource] {
149        &self.initial_inputs
150    }
151
152    pub fn environment_blend_mode(&self) -> EnvironmentBlendMode {
153        self.environment_blend_mode
154    }
155
156    pub fn viewports(&self) -> &[Rect<i32, Viewport>] {
157        &self.viewports.viewports
158    }
159
160    /// A resolution large enough to contain all the viewports.
161    /// <https://immersive-web.github.io/webxr/#recommended-webgl-framebuffer-resolution>
162    ///
163    /// Returns None if the session is inline
164    pub fn recommended_framebuffer_resolution(&self) -> Option<Size2D<i32, Viewport>> {
165        self.viewports()
166            .iter()
167            .fold(None::<Rect<_, _>>, |acc, vp| {
168                Some(acc.map(|a| a.union(vp)).unwrap_or(*vp))
169            })
170            .map(|rect| Size2D::new(rect.max_x(), rect.max_y()))
171    }
172
173    pub fn create_layer(&self, context_id: ContextId, init: LayerInit) -> Result<LayerId, Error> {
174        let Some((sender, receiver)) = generic_channel::channel() else {
175            return Err(Error::CommunicationError);
176        };
177        let _ = self
178            .sender
179            .send(SessionMsg::CreateLayer(context_id, init, sender));
180        receiver.recv().map_err(|_| Error::CommunicationError)?
181    }
182
183    /// Destroy a layer
184    pub fn destroy_layer(&self, context_id: ContextId, layer_id: LayerId) {
185        let _ = self
186            .sender
187            .send(SessionMsg::DestroyLayer(context_id, layer_id));
188    }
189
190    pub fn set_layers(&self, layers: Vec<(ContextId, LayerId)>) {
191        let _ = self.sender.send(SessionMsg::SetLayers(layers));
192    }
193
194    pub fn start_render_loop(&mut self) {
195        let _ = self.sender.send(SessionMsg::StartRenderLoop);
196    }
197
198    pub fn update_clip_planes(&mut self, near: f32, far: f32) {
199        let _ = self.sender.send(SessionMsg::UpdateClipPlanes(near, far));
200    }
201
202    pub fn set_event_dest(&mut self, dest: ProfileGenericCallback<Event>) {
203        let _ = self.sender.send(SessionMsg::SetEventDest(dest));
204    }
205
206    pub fn set_frame_dest(&mut self, dest: ProfileGenericCallback<Frame>) {
207        let _ = self.sender.send(SessionMsg::SetFrameDest(dest));
208    }
209
210    pub fn render_animation_frame(&mut self) {
211        let _ = self.sender.send(SessionMsg::RenderAnimationFrame);
212    }
213
214    pub fn end_session(&mut self) {
215        let _ = self.sender.send(SessionMsg::Quit);
216    }
217
218    pub fn apply_event(&mut self, event: FrameUpdateEvent) {
219        match event {
220            FrameUpdateEvent::UpdateFloorTransform(floor) => self.floor_transform = floor,
221            FrameUpdateEvent::UpdateViewports(vp) => self.viewports = vp,
222            FrameUpdateEvent::HitTestSourceAdded(_) => (),
223        }
224    }
225
226    pub fn granted_features(&self) -> &[String] {
227        &self.granted_features
228    }
229
230    pub fn request_hit_test(&self, source: HitTestSource) {
231        let _ = self.sender.send(SessionMsg::RequestHitTest(source));
232    }
233
234    pub fn cancel_hit_test(&self, id: HitTestId) {
235        let _ = self.sender.send(SessionMsg::CancelHitTest(id));
236    }
237
238    pub fn update_frame_rate(&mut self, rate: f32, sender: ProfileGenericCallback<f32>) {
239        let _ = self.sender.send(SessionMsg::UpdateFrameRate(rate, sender));
240    }
241
242    pub fn supported_frame_rates(&self) -> &[f32] {
243        &self.supported_frame_rates
244    }
245}
246
247#[derive(PartialEq)]
248enum RenderState {
249    NotInRenderLoop,
250    InRenderLoop,
251    PendingQuit,
252}
253
254/// For devices that want to do their own thread management, the `SessionThread` type is exposed.
255pub struct SessionThread<Device> {
256    receiver: GenericReceiver<SessionMsg>,
257    sender: GenericSender<SessionMsg>,
258    layers: Vec<(ContextId, LayerId)>,
259    pending_layers: Option<Vec<(ContextId, LayerId)>>,
260    frame_count: u64,
261    frame_sender: Option<ProfileGenericCallback<Frame>>,
262    running: bool,
263    device: Device,
264    id: SessionId,
265    render_state: RenderState,
266}
267
268impl<Device> SessionThread<Device>
269where
270    Device: DeviceAPI,
271{
272    pub fn new(mut device: Device, id: SessionId) -> Result<Self, Error> {
273        let Some((sender, receiver)) = generic_channel::channel() else {
274            return Err(Error::CommunicationError);
275        };
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: None,
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::SetFrameDest(dest) => {
333                self.frame_sender = Some(dest);
334            },
335            SessionMsg::RequestHitTest(source) => {
336                self.device.request_hit_test(source);
337            },
338            SessionMsg::CancelHitTest(id) => {
339                self.device.cancel_hit_test(id);
340            },
341            SessionMsg::CreateLayer(context_id, layer_init, sender) => {
342                let result = self.device.create_layer(context_id, layer_init);
343                let _ = sender.send(result);
344            },
345            SessionMsg::DestroyLayer(context_id, layer_id) => {
346                self.layers.retain(|&(_, other_id)| layer_id != other_id);
347                self.device.destroy_layer(context_id, layer_id);
348            },
349            SessionMsg::SetLayers(layers) => {
350                self.pending_layers = Some(layers);
351            },
352            SessionMsg::StartRenderLoop => {
353                if let Some(layers) = self.pending_layers.take() {
354                    self.layers = layers;
355                }
356                let frame = match self.device.begin_animation_frame(&self.layers[..]) {
357                    Some(frame) => frame,
358                    None => {
359                        warn!("Device stopped providing frames, exiting");
360                        return false;
361                    },
362                };
363                self.render_state = RenderState::InRenderLoop;
364                self.send_frame(frame);
365            },
366            SessionMsg::UpdateClipPlanes(near, far) => self.device.update_clip_planes(near, far),
367            SessionMsg::RenderAnimationFrame => {
368                self.frame_count += 1;
369
370                self.device.end_animation_frame(&self.layers[..]);
371
372                if self.render_state == RenderState::PendingQuit {
373                    self.quit();
374                    return false;
375                }
376
377                if let Some(layers) = self.pending_layers.take() {
378                    self.layers = layers;
379                }
380                #[expect(unused_mut)]
381                let mut frame = match self.device.begin_animation_frame(&self.layers[..]) {
382                    Some(frame) => frame,
383                    None => {
384                        warn!("Device stopped providing frames, exiting");
385                        return false;
386                    },
387                };
388
389                self.send_frame(frame);
390            },
391            SessionMsg::UpdateFrameRate(rate, sender) => {
392                let new_framerate = self.device.update_frame_rate(rate);
393                let _ = sender.send(new_framerate);
394            },
395            SessionMsg::Quit => {
396                if self.render_state == RenderState::NotInRenderLoop {
397                    self.quit();
398                    return false;
399                } else {
400                    self.render_state = RenderState::PendingQuit;
401                }
402            },
403            SessionMsg::GetBoundsGeometry(sender) => {
404                let bounds = self.device.reference_space_bounds();
405                let _ = sender.send(bounds);
406            },
407        }
408        true
409    }
410
411    fn send_frame(&self, frame: Frame) {
412        if let Some(frame_sender) = &self.frame_sender {
413            let _ = frame_sender.send(frame);
414        }
415    }
416
417    fn quit(&mut self) {
418        self.render_state = RenderState::NotInRenderLoop;
419        self.device.quit();
420    }
421}
422
423/// Devices that need to can run sessions on the main thread.
424pub trait MainThreadSession: 'static {
425    fn run_one_frame(&mut self);
426    fn running(&self) -> bool;
427}
428
429impl<Device> MainThreadSession for SessionThread<Device>
430where
431    Device: DeviceAPI,
432{
433    fn run_one_frame(&mut self) {
434        let frame_count = self.frame_count;
435        while frame_count == self.frame_count && self.running {
436            if let Ok(msg) = self.receiver.try_recv_timeout(TIMEOUT) {
437                self.running = self.handle_msg(msg);
438            } else {
439                break;
440            }
441        }
442    }
443
444    fn running(&self) -> bool {
445        self.running
446    }
447}
448
449/// A type for building XR sessions
450pub struct SessionBuilder<'a, GL> {
451    sessions: &'a mut Vec<Box<dyn MainThreadSession>>,
452    layer_grand_manager: LayerGrandManager<GL>,
453    id: SessionId,
454}
455
456impl<'a, GL: 'static> SessionBuilder<'a, GL> {
457    pub fn id(&self) -> SessionId {
458        self.id
459    }
460
461    pub(crate) fn new(
462        sessions: &'a mut Vec<Box<dyn MainThreadSession>>,
463        layer_grand_manager: LayerGrandManager<GL>,
464        id: SessionId,
465    ) -> Self {
466        SessionBuilder {
467            sessions,
468            layer_grand_manager,
469            id,
470        }
471    }
472
473    /// For devices which are happy to hand over thread management to webxr.
474    pub fn spawn<Device, Factory>(self, factory: Factory) -> Result<Session, Error>
475    where
476        Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error> + Send,
477        Device: DeviceAPI,
478    {
479        let Some((acks, ackr)) = generic_channel::channel() else {
480            return Err(Error::CommunicationError);
481        };
482        let layer_grand_manager = self.layer_grand_manager;
483        let id = self.id;
484        thread::spawn(move || {
485            match factory(layer_grand_manager).and_then(|device| SessionThread::new(device, id)) {
486                Ok(mut thread) => {
487                    let session = thread.new_session();
488                    let _ = acks.send(Ok(session));
489                    thread.run();
490                },
491                Err(err) => {
492                    let _ = acks.send(Err(err));
493                },
494            }
495        });
496        ackr.recv().unwrap_or(Err(Error::CommunicationError))
497    }
498
499    /// For devices that need to run on the main thread.
500    pub fn run_on_main_thread<Device, Factory>(self, factory: Factory) -> Result<Session, Error>
501    where
502        Factory: 'static + FnOnce(LayerGrandManager<GL>) -> Result<Device, Error>,
503        Device: DeviceAPI,
504    {
505        let device = factory(self.layer_grand_manager)?;
506        let mut session_thread = SessionThread::new(device, self.id)?;
507        let session = session_thread.new_session();
508        self.sessions.push(Box::new(session_thread));
509        Ok(session)
510    }
511}